diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000..a82f419738 Binary files /dev/null and b/.DS_Store differ diff --git a/.env b/.env new file mode 100644 index 0000000000..68eb7dd8dd --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +GITHUB_CLIENT_ID: 7de823906fd607e07f3d +GITHUB_CLIENT_SECRET: a7fd0670681c55d342e8580b38c2a95b74286ba1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..cf7ce543cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +/node_modules +/yarn-error.log + +.byebug_history + +.env +/coverage/* diff --git a/Alibonbon_ERD.jpeg b/Alibonbon_ERD.jpeg new file mode 100644 index 0000000000..6523bc003c Binary files /dev/null and b/Alibonbon_ERD.jpeg differ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..4b4fd46be0 --- /dev/null +++ b/Gemfile @@ -0,0 +1,81 @@ +source 'https://rubygems.org' + +ruby '2.5.0' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + +gem 'simplecov', require: true, group: :test +# 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' +# 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 + +gem "omniauth" +gem "omniauth-github" + +gem 'will_paginate' + + +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' + + gem 'dotenv-rails' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'jquery-turbolinks' +gem 'jquery-rails' +gem 'foundation-rails' +gem 'autoprefixer-rails' +gem 'normalize-rails' +group :development, :test do + gem 'pry-rails' +end + +group :development do + gem 'better_errors' + gem 'binding_of_caller' +end + +group :test do + gem 'minitest-rails' + gem 'minitest-reporters' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..096b0251ee --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,279 @@ +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) + autoprefixer-rails (8.2.0) + execjs + 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) + coderay (1.1.2) + concurrent-ruby (1.0.5) + crass (1.0.4) + debug_inspector (0.0.3) + docile (1.3.0) + dotenv (2.2.2) + dotenv-rails (2.2.2) + dotenv (= 2.2.2) + 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 + 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) + 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.9) + 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) + will_paginate (3.1.6) + xpath (3.0.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + autoprefixer-rails + better_errors + binding_of_caller + byebug + capybara (~> 2.13) + 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) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + will_paginate + +RUBY VERSION + ruby 2.5.0p0 + +BUNDLED WITH + 1.16.1 diff --git a/README.md b/README.md index 2758975bf5..7db80e4ca1 100644 --- a/README.md +++ b/README.md @@ -1,205 +1,24 @@ -# bEtsy -[Much like other e-commerce platforms](https://www.etsy.com/), your team will make an online store where a wide variety of products can be listed and sold by any user. This project focuses on reinforcing the major components of Rails, model validations, testing, and more complex logic such as user authentication. +# README -This is a [Stage 3](https://github.com/Ada-Developers-Academy/pedagogy/blob/master/rule-of-three.md) project requiring you to expand upon what you have learned in class. +This README would normally document whatever steps are necessary to get the +application up and running. -## Project Learning Goals -- Core comprehension of: - - Routes - - Controllers - - Models - - Views -- User based application logic -- User authentication -- Testing on models and controllers -- Agile practices -- Feature branch management with Git -- Group project ownership +Things you may want to cover: -## Guidelines -- Groups of three or four will collaborate in pairs or individually and will report to their assigned Project Manager (one of the instructors) -- Use a task manager like [Trello](http://trello.com) to track your team's efforts -- Build a logical user-flow that moves across multiple controllers and models -- Use HTML/CSS and Foundation to style your website +* Ruby version -## Getting Started -1. As a group decide on an app name (this may help lead the aesthetic) -1. As a group decide on a team name (this will amuse your instructors) -1. Have one person on your team fork/clone the project master as per usual - 1. Create a new rails app using `rails new .` - 1. Add all other team members as collaborators - 1. Each team member should clone the repo to their computer -1. Figure out your workflow for the project, re: Git and Task management - 1. Do you want to use git branches? Pull requests? - 1. Determine who will be the Stand Up Leader and Task Leader for the first week -1. Create a Trello board to use as a Kanban board and ensure that all team members and instructors have access -1. Review the User Stories below and create Trello tasks to represent them -1. Slack your team name, app name, and link to your Trello board to your Project Manager +* System dependencies -## Expectations -Build an online system for listing, selling, reviewing, and buying a wide variety of products listed by multiple merchants. +* Configuration -### General Requirements -- Unit tests and/or specs for - - Models - - Controllers -- Test code coverage (using SimpleCov - remember me!) - - 90% for all controller and model classes +* Database creation -### User Stories -#### Guest User (Unauthenticated) -As a guest to the website (not signed in) I **can**: +* Database initialization -- Browse all products -- Browse products by category -- Browse products by merchant (users) -- View any individual product with additional details -- Leave a review for a product providing: - - A text review - - A rating out of 5 -- Add in-stock products to my cart -- Remove products from my cart -- Change the quantity of an existing product in my cart -- Purchase the items in my cart, providing: - - Email Address - - Mailing Address - - Name on credit card - - Credit card number - - Credit cart expiration - - Credit Card CVV (security code) - - Billing zip code -- Purchasing an order makes the following changes: - - Reduces the number of inventory for each product - - Changes the order state from "pending" to "paid" - - Clears the current cart -- After purchasing an order, I can view a confirmation screen including: - - Each item in the order with a quantity and line-item subtotal - - A link to the item description page - - Order total price - - Date/time the order was placed - - The current status of the order -- Sign up to be a merchant using OAuth - - Every merchant must have a username -- Sign in to my merchant account using OAuth +* How to run the test suite -As a guest I **cannot**: +* Services (job queues, cache servers, search engines, etc.) -- Add products to the cart that are out of stock -- View any link or page to manage any products -- View any of the account pages +* Deployment instructions -#### Authenticated Users -As a signed-in user, I **can**: - -- Do everything a guest user can do except for sign up and sign in -- Sign out -- Create new categories (categories are shared between all merchants) -- Create a new product providing: - - name - - description - - price - - photo URL - - stock -- Assign my products to any number of categories -- Retire a product from being sold, which hides it from browsing -- View an account page to edit/update my existing products -- View an account page showing my order fulfillment -- On the order fulfillment page: - - Total Revenue - - Total Revenue by status - - Total number of orders by status - - Filter orders displayed by status - - Link to each individual order - - A list of orders including at least one of my products: - - Each order item sold by me with a quantity and line-item subtotal - - A link to the item description page - - DateTime the order was placed - - Link to transition the order item to marked as shipped - - The current status of the order ("pending", "paid", "complete", "cancelled") -- View an individual order to see the user's: - - Name - - Email address - - Mailing address - - Last four digits of their credit card - - Credit card expiration date - -As a signed-in user, I **cannot**: - -- Review my own products -- View order items from a shared order that belong to another merchant -- View another user's private data (i.e. order fulfillment or product management) - -### Validations -Many of our models will have attributes that are required for our application to use and display data consistently. Each model will have attributes with requirements for a valid record. The requirements are summarized below: - -#### Merchant -- Username must be present -- Username must be unique -- Email Address must be present -- Email Address must be unique - -#### Product -- Name must be present -- Name must be unique -- Price must be present -- Price must be a number -- Price must be greater than 0 -- Product must belong to a User - -#### Order -- An Order must have one or more Order Items - -#### OrderItem -- Must belong to a Product -- Must belong to an Order -- Quantity must be present -- Quantity must be an integer -- Quantity must be greater than 0 - -#### Review -- Rating must be present -- Rating must be an integer -- Rating must be between 1 and 5 - -## Submission Guidelines -Your final project must be deployed to [Heroku](http://heroku.com). Your team will open a single pull request for the entire project. There are comprehension questions to answer with your submission that you should complete together as a group. Remember, you can submit a PR and still make some final changes to your code, so don't wait until the last minute. - -## Team Leaders -Each team will have team leaders who are responsible for keeping track of each team member's contributions. Rotate leader roles at the beginning of the second week; every team member should be in at least one leader role during the project. - -- **Stand Up Leader** - - Notifies team members about meeting schedule and ensures that everyone is present and ready - - Takes notes about each person's daily report in Stand Up - - Keeps the meeting moving -- **Task Leader** - - Leads discussion on task assignment and prioritization - - Decide if a task should be completed alone or in a pair - - Assign tasks based on... - - Individual comfort - - Desire - - Ability - - Ensures the Kanban board stays up to date - -## Stand Up Meetings -The Stand Up Leader should determine the daily time for your stand up meeting with the team. Once you come up with a time, confirm with your PM that this time will work for them. - -At the end of each day, your team's assigned Project Manager will review the Trello board to ensure it captures the updates that your team has made throughout the day. - -## Interim Demo -In a real world work environment, a team's success is measured by their product as opposed to each individual's contribution. - -Each team will present their progress and respond to questions from their Project Manager on the first Friday. Every team member will participate in these demos; the PM will ask specific questions regarding -1. The team's progress and plan for completing the project -1. The technical decisions and implementation -1. Every team member's understanding of the underlying technical structures - -## Final Presentation -Each team will present their product in a final presentation to the group on the final Friday. Your presentation should be no more than 7 minutes. The presentation should include every team member and: -- what you learned as individuals and as a group -- a short story-driven demo of interesting features - -## Due Date -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. +* ... 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/cake.png b/app/assets/images/cake.png new file mode 100644 index 0000000000..6bbfcb7578 Binary files /dev/null and b/app/assets/images/cake.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000000..bbeccf9c77 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,19 @@ +// 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_tree . + +$(function(){ $(document).foundation(); }); diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 0000000000..739aa5f022 --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/app/assets/javascripts/cartitems.js b/app/assets/javascripts/cartitems.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/cartitems.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/carts.js b/app/assets/javascripts/carts.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/carts.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/merchants.js b/app/assets/javascripts/merchants.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/merchants.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/orders.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/products.js b/app/assets/javascripts/products.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/products.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/reviews.js b/app/assets/javascripts/reviews.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/app/assets/javascripts/reviews.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/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/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss new file mode 100644 index 0000000000..f22f7b05ec --- /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: $dark-gray; +$orbit-bullet-diameter: 1.2rem; +$orbit-bullet-margin: 0.1rem; +$orbit-bullet-margin-top: 0.8rem; +$orbit-bullet-margin-bottom: 0.8rem; +$orbit-caption-background: rgba($black, 0.5); +$orbit-caption-padding: 1rem; +$orbit-control-background-hover: rgba($black, 0.5); +$orbit-control-padding: 1rem; +$orbit-control-zindex: 10; + +// 27. Pagination +// -------------- + +$pagination-font-size: rem-calc(14); +$pagination-margin-bottom: $global-margin; +$pagination-item-color: $black; +$pagination-item-padding: rem-calc(3 10); +$pagination-item-spacing: rem-calc(1); +$pagination-radius: $global-radius; +$pagination-item-background-hover: $light-gray; +$pagination-item-background-current: $primary-color; +$pagination-item-color-current: $white; +$pagination-item-color-disabled: $medium-gray; +$pagination-ellipsis-color: $black; +$pagination-mobile-items: false; +$pagination-mobile-current-item: false; +$pagination-arrows: true; + +// 28. Progress Bar +// ---------------- + +$progress-height: 1rem; +$progress-background: $medium-gray; +$progress-margin-bottom: $global-margin; +$progress-meter-background: $primary-color; +$progress-radius: $global-radius; + +// 29. Prototype Arrow +// ------------------- + +$prototype-arrow-directions: ( + down, + up, + right, + left +); +$prototype-arrow-size: 0.4375rem; +$prototype-arrow-color: $black; + +// 30. Prototype Border-Box +// ------------------------ + +$prototype-border-box-breakpoints: $global-prototype-breakpoints; + +// 31. Prototype Border-None +// ------------------------- + +$prototype-border-none-breakpoints: $global-prototype-breakpoints; + +// 32. Prototype Bordered +// ---------------------- + +$prototype-bordered-breakpoints: $global-prototype-breakpoints; +$prototype-border-width: rem-calc(1); +$prototype-border-type: solid; +$prototype-border-color: $medium-gray; + +// 33. Prototype Display +// --------------------- + +$prototype-display-breakpoints: $global-prototype-breakpoints; +$prototype-display: ( + inline, + inline-block, + block, + table, + table-cell +); + +// 34. Prototype Font-Styling +// -------------------------- + +$prototype-font-breakpoints: $global-prototype-breakpoints; +$prototype-wide-letter-spacing: rem-calc(4); +$prototype-font-normal: $global-weight-normal; +$prototype-font-bold: $global-weight-bold; + +// 35. Prototype List-Style-Type +// ----------------------------- + +$prototype-list-breakpoints: $global-prototype-breakpoints; +$prototype-style-type-unordered: ( + disc, + circle, + square +); +$prototype-style-type-ordered: ( + decimal, + lower-alpha, + lower-latin, + lower-roman, + upper-alpha, + upper-latin, + upper-roman +); + +// 36. Prototype Overflow +// ---------------------- + +$prototype-overflow-breakpoints: $global-prototype-breakpoints; +$prototype-overflow: ( + visible, + hidden, + scroll +); + +// 37. Prototype Position +// ---------------------- + +$prototype-position-breakpoints: $global-prototype-breakpoints; +$prototype-position: ( + static, + relative, + absolute, + fixed +); +$prototype-position-z-index: 975; + +// 38. Prototype Rounded +// --------------------- + +$prototype-rounded-breakpoints: $global-prototype-breakpoints; +$prototype-border-radius: rem-calc(3); + +// 39. Prototype Separator +// ----------------------- + +$prototype-separator-breakpoints: $global-prototype-breakpoints; +$prototype-separator-align: center; +$prototype-separator-height: rem-calc(2); +$prototype-separator-width: 3rem; +$prototype-separator-background: $primary-color; +$prototype-separator-margin-top: $global-margin; + +// 40. Prototype Shadow +// -------------------- + +$prototype-shadow-breakpoints: $global-prototype-breakpoints; +$prototype-box-shadow: 0 2px 5px 0 rgba(0,0,0,.16), + 0 2px 10px 0 rgba(0,0,0,.12); + +// 41. Prototype Sizing +// -------------------- + +$prototype-sizing-breakpoints: $global-prototype-breakpoints; +$prototype-sizing: ( + width, + height +); +$prototype-sizes: ( + 25: 25%, + 50: 50%, + 75: 75%, + 100: 100% +); + +// 42. Prototype Spacing +// --------------------- + +$prototype-spacing-breakpoints: $global-prototype-breakpoints; +$prototype-spacers-count: 3; + +// 43. Prototype Text-Decoration +// ----------------------------- + +$prototype-decoration-breakpoints: $global-prototype-breakpoints; +$prototype-text-decoration: ( + overline, + underline, + line-through, +); + +// 44. Prototype Text-Transformation +// --------------------------------- + +$prototype-transformation-breakpoints: $global-prototype-breakpoints; +$prototype-text-transformation: ( + lowercase, + uppercase, + capitalize +); + +// 45. Prototype Text-Utilities +// ---------------------------- + +$prototype-utilities-breakpoints: $global-prototype-breakpoints; +$prototype-text-overflow: ellipsis; + +// 46. Responsive Embed +// -------------------- + +$responsive-embed-margin-bottom: rem-calc(16); +$responsive-embed-ratios: ( + default: 4 by 3, + widescreen: 16 by 9, +); + +// 47. Reveal +// ---------- + +$reveal-background: $white; +$reveal-width: 600px; +$reveal-max-width: $global-width; +$reveal-padding: $global-padding; +$reveal-border: 1px solid $medium-gray; +$reveal-radius: $global-radius; +$reveal-zindex: 1005; +$reveal-overlay-background: rgba($black, 0.45); + +// 48. Slider +// ---------- + +$slider-width-vertical: 0.5rem; +$slider-transition: all 0.2s ease-in-out; +$slider-height: 0.5rem; +$slider-background: $light-gray; +$slider-fill-background: $medium-gray; +$slider-handle-height: 1.4rem; +$slider-handle-width: 1.4rem; +$slider-handle-background: $primary-color; +$slider-opacity-disabled: 0.25; +$slider-radius: $global-radius; + +// 49. Switch +// ---------- + +$switch-background: $medium-gray; +$switch-background-active: $primary-color; +$switch-height: 2rem; +$switch-height-tiny: 1.5rem; +$switch-height-small: 1.75rem; +$switch-height-large: 2.5rem; +$switch-radius: $global-radius; +$switch-margin: $global-margin; +$switch-paddle-background: $white; +$switch-paddle-offset: 0.25rem; +$switch-paddle-radius: $global-radius; +$switch-paddle-transition: all 0.25s ease-out; + +// 50. Table +// --------- + +$table-background: $white; +$table-color-scale: 5%; +$table-border: 1px solid smart-scale($table-background, $table-color-scale); +$table-padding: rem-calc(8 10 10); +$table-hover-scale: 2%; +$table-row-hover: darken($table-background, $table-hover-scale); +$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale); +$table-is-striped: true; +$table-striped-background: smart-scale($table-background, $table-color-scale); +$table-stripe: even; +$table-head-background: smart-scale($table-background, $table-color-scale / 2); +$table-head-row-hover: darken($table-head-background, $table-hover-scale); +$table-foot-background: smart-scale($table-background, $table-color-scale); +$table-foot-row-hover: darken($table-foot-background, $table-hover-scale); +$table-head-font-color: $body-font-color; +$table-foot-font-color: $body-font-color; +$show-header-for-stacked: false; +$table-stack-breakpoint: medium; + +// 51. Tabs +// -------- + +$tab-margin: 0; +$tab-background: $white; +$tab-color: $primary-color; +$tab-background-active: $light-gray; +$tab-active-color: $primary-color; +$tab-item-font-size: rem-calc(12); +$tab-item-background-hover: $white; +$tab-item-padding: 1.25rem 1.5rem; +$tab-expand-max: 6; +$tab-content-background: $white; +$tab-content-border: $light-gray; +$tab-content-color: $body-font-color; +$tab-content-padding: 1rem; + +// 52. Thumbnail +// ------------- + +$thumbnail-border: solid 4px $white; +$thumbnail-margin-bottom: $global-margin; +$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2); +$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); +$thumbnail-transition: box-shadow 200ms ease-out; +$thumbnail-radius: $global-radius; + +// 53. Title Bar +// ------------- + +$titlebar-background: $black; +$titlebar-color: $white; +$titlebar-padding: 0.5rem; +$titlebar-text-font-weight: bold; +$titlebar-icon-color: $white; +$titlebar-icon-color-hover: $medium-gray; +$titlebar-icon-spacing: 0.25rem; + +// 54. Tooltip +// ----------- + +$has-tip-cursor: help; +$has-tip-font-weight: $global-weight-bold; +$has-tip-border-bottom: dotted 1px $dark-gray; +$tooltip-background-color: $black; +$tooltip-color: $white; +$tooltip-padding: 0.75rem; +$tooltip-max-width: 10rem; +$tooltip-font-size: $small-font-size; +$tooltip-pip-width: 0.75rem; +$tooltip-pip-height: $tooltip-pip-width * 0.866; +$tooltip-radius: $global-radius; + +// 55. Top Bar +// ----------- + +$topbar-padding: 0.5rem; +$topbar-background: rgba(193, 84, 246, 0.78); +$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..d59cb3f72b --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,159 @@ +/* +* 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 + +*/ +html { + height: 100%; + width: 100%; + margin: auto; +} +a { + color: rgb(64, 34, 2); +} +body { + color: rgb(64, 34, 2); + height: 100%; + + /* Full height */ + height: 100%; + width: 100%; + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +body { + font-family: 'Berkshire Swash';font-size: 22px; + font-weight: lighter; +} +.image-header a { + font-family: 'Berkshire Swash';font-size: 75px; + text-align: center; + background-color: rgb(192, 242, 205); +} +.image-header a { + color: rgb(64, 34, 2); +} +ul.menu.toggle { + background-color: rgb(192, 242, 205); +} + +.button-group { + float: right; +} +header { + color: rgb(64, 34, 2); + background-color: #3adb765c; + border-top-style:double; + border-bottom-style:double; + padding-top:5px; + padding-bottom:5px; + border-top-color:inherit; + border-bottom-color:inherit; + border-top-width:20px; + border-bottom-width:20px; +} + +a.float-right { + padding-left: 10px; + padding-right: 10px; +} + +div.top-bar { + background-color: rgb(192, 242, 205); +} + +#nav-list > .top-bar > .top-bar-left ul { + background-color: rgb(192, 242, 205); +} +body .button { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); + border: 2px solid rgb(64, 34, 2); +} + +body .button:hover { + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); +} + +main { + color: rgb(64, 34, 2); + border: 10px double rgb(64, 34, 2); + background-color: rgb(192, 242, 205); +} + +img.orbit-image { + border: 10px solid rgb(64, 34, 2); + border-width: 5px; + margin-top: 30px; +} + +footer { + margin-top: 2em; + height: 15%; + color: rgb(64, 34, 2); + border: 10px double rgb(64, 34, 2); + background-color: rgb(192, 242, 205); +} + +.ownership ul { + margin: auto; + display: flex; + flex-direction: row; + justify-content: space-around; +} +.ownership p { + padding-left: 10px; +} +.pagination .current { + background-color: rgb(64, 34, 2); +} + +#nav-list .button { + margin: 20px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.1rem; +} + +#nav-list .button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +#new-product-form .button { + margin: 20px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.1rem; +} + +#new-product-form .button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); + +} + +#new-product-form { + margin: 30px; +} + +#distance { + margin-right: 500px; +} 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/cartitems.scss b/app/assets/stylesheets/cartitems.scss new file mode 100644 index 0000000000..5ae554e0c1 --- /dev/null +++ b/app/assets/stylesheets/cartitems.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the cartitems 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/carts.scss b/app/assets/stylesheets/carts.scss new file mode 100644 index 0000000000..4294fe7f67 --- /dev/null +++ b/app/assets/stylesheets/carts.scss @@ -0,0 +1,44 @@ +// Place all the styles related to the carts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + + +.cart-page .double-button { + margin: 20px 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.2rem; +} + +.cart-page .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.order-new .lowerbar .double-button { + margin: 20px 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.2rem; +} + +.order-new .lowerbar .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +#form-order .double-button { + margin: 20px 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.2rem; +} + +#form-order .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.edit-order, .order-new, .cart-page{ + margin: 20px; +} diff --git a/app/assets/stylesheets/categories.scss b/app/assets/stylesheets/categories.scss new file mode 100644 index 0000000000..cd0c304aed --- /dev/null +++ b/app/assets/stylesheets/categories.scss @@ -0,0 +1,63 @@ +// 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/ + + +.cate-display { + list-style-type: none; + display: flex; + flex-wrap: wrap; + justify-content: center; +} +.cate-display > li { + margin: 10px 40px; +} + +.small-pro { + width: 250px; + height: 200px; + border-radius: 30%; + border: 1px solid rgb(64, 34, 2); +} + +.small-pro:hover { + border-radius: 50%; +} + +.all-categories h2 { + text-align: left; +} + +.all-categories .pro-name { + width: 250px; + height: 120px; + text-align: center; +} + +.show-category .grid-container { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + grid-gap: 50px; + margin-bottom: 3em; +} +.product-cat-img { + display: grid; + align-items: center; + width: 400px; + height: 400px; + border: 10px double rgb(64, 34, 2); + border-radius: 50%; +} +h2 { + display: flex; + justify-content: center; +} +.category-name { + padding-top: 20px; + padding-bottom: 20px; +} +.product-stuff li { + font-size: 26px; + font-family: "Helvetica", sans-serif; +} diff --git a/app/assets/stylesheets/foundation_and_overrides.scss b/app/assets/stylesheets/foundation_and_overrides.scss new file mode 100644 index 0000000000..2595ef6d0c --- /dev/null +++ b/app/assets/stylesheets/foundation_and_overrides.scss @@ -0,0 +1,82 @@ +@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; + + +.status { + border-radius: 5px; + background-color: $light-gray; + padding: 20px 25px; + margin-bottom: 20px; + color: $dark-gray; +} + + +.status.failure { + h3 { + color: #C70039; + } +} + +.status.failure, .status.success { + h3 { + font-weight: normal; + font-size: 1em; + } +} diff --git a/app/assets/stylesheets/merchants.scss b/app/assets/stylesheets/merchants.scss new file mode 100644 index 0000000000..831fccb4a4 --- /dev/null +++ b/app/assets/stylesheets/merchants.scss @@ -0,0 +1,50 @@ +.merchant-display { + list-style-type: none; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.merchant-display > li { + margin: 10px 40px; +} + +.middle-pro { + width: 250px; + height: 200px; +} + +.middle-pro:hover { + border-radius: 50%; +} + +.all-merchants h2 { + text-align: left; +} + +.all-merchants .pro-name { + width: 250px; + height: 120px; + text-align: center; +} + +.show-merchant > ul { + list-style-type: none; +} + +.show-merchant { + padding: 20px 20px; +} + +.edit-product, .display-products, .create-category { + padding: 30px 50px; +} + + +.show-merchant-products .grid-container { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + grid-gap: 50px; + margin-bottom: 3em; +} diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss new file mode 100644 index 0000000000..4c24b28dd5 --- /dev/null +++ b/app/assets/stylesheets/orders.scss @@ -0,0 +1,180 @@ +// 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/ +.lowerbar { + display: flex; +} +.lowerbar a { + margin-left: 6px; + margin-right: 6px; +} + +.display-my-order, .orders-display { + margin: 30px 150px; + padding-bottom: 60px; +} + +.display-my-order > ul { + list-style-type: none; +} +.display-my-order .button { + margin-right: 60px; +} + +.display-products h4 { + padding-left: 30px; + font-weight: bold; + margin: 20px auto; +} + +.table-orders .table-head .small { + width: 200px; +} + +.display-products .double-button { + margin-left: 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.5rem; +} + +.display-products .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.show-merchant .button { + margin: auto 20px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.5rem; +} + +.show-merchant .button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.orders-display .double-button { + margin-left: 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.5rem; + +} + +.orders-display .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.display-my-order .double-button { + margin-left: 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.5rem; +} + +.display-my-order .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.new-product-form .double-button { + margin-left: 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.5rem; +} + +.new-product-form .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.new-category-form .double-button { + margin:auto 30px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); + font-size: 1.2rem; +} + +.new-category-form .double-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +.orders-display .orders-table .small { + width: 100px; + padding-left: 30px; +} + +.orders-display .orders-table .middle { + width: 200px; +} + +.orders-display .orders-table .large { + width: 400px; +} + +.status-bar { + display: inline-block; + width: 100px; +} + +#search-button { + margin: auto 20px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); +} + +#search-button:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +#search-field { + display: inline; + width: 600px; +} + +#search-bar { + justify-content: center; + text-align: center; +} + +#example-tabs .tabs-title > a { + color: rgb(64, 34, 2); + font-size: 1.2rem; +} + +#example-tabs .tabs-title > a:hover { + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); +} + +.tabs-panel > p { + font-size: 2rem; +} + + +#search-button-2 { + margin: 20px; + background-color: rgb(64, 34, 2); + color: rgb(192, 242, 205); +} + +#search-button-2:hover { + background-color: rgb(192, 242, 205); + color: rgb(64, 34, 2); +} + +#search-field-2 { + display: inline; + width: 600px; +} + +#search-bar-2 { + justify-content: center; + text-align: center; +} diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss new file mode 100644 index 0000000000..98c397a8f6 --- /dev/null +++ b/app/assets/stylesheets/products.scss @@ -0,0 +1,172 @@ +// 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/ + +header { + max-width: 100%; + background: rgba(193, 84, 246, 0.78); + background-repeat: no-repeat; + border-bottom: 60px rgb(64, 34, 2); + margin-bottom: 60px; + margin-left: auto; + margin-right: auto; + color: rgb(64, 34, 2); +} +h2 { + font-family: 'Berkshire Swash';font-size: 45px; + font-weight: lighter; +} + +// Image styling +.orbit { + width: 50%; + height: 50%; + margin-left: auto; + margin-right: auto; +} +.bonbon-img { + width: 900px; + display: block; + align-items: center; + justify-content: center; + border-radius: 50%; + border: 10px double rgb(64, 34, 2); +} +.image-container { + display: flex; + margin: auto; + align-items: center; + justify-content: center; + width: 45%; + padding-bottom: 100px; + padding-top: 100px; + padding-left: 60px; +} + + +.pagination { + display: flex; + flex-direction: row; + justify-content: center; + font-size: 23px; +} +em.current { + border-radius: 5px; +} +.product_pic { + border: 1px solid rgb(64, 34, 2); + border-radius: 4px; /* Rounded border */ + padding: 5px; /* Some padding */ + width: 150px; /* Set a small width */ +} + +// Product Info and Details Stylings +section.product-show-page { + display: flex; + flex-wrap: wrap; + align-content: space-around; + padding: 10px 10px 40px 10px; +} +div.product-info-container { + width: 600px; + margin: auto; + margin-bottom: 5em; + border: 10px double rgb(64, 34, 2); + border-radius: 8px; + text-align: center; + padding: 10px 10px 10px 10px; + float: left; +} +div.rating-form-container { + width: 100%; + height: 390px; + margin: auto; + margin-left: 2em; + margin-right: 2em; + border: 10px double rgb(64, 34, 2); + border-radius: 8px; + text-align: center; + padding: 10px 10px 10px 10px; +} + +// Ratings and Reviews Info Styling +section.all-ratings-box { + display: flex; + justify-content: center; + align-content: space-around; + padding: 20px 20px 20px 20px; +} +div.all-reviews-container { + display: grid; + width: 800px; + margin-bottom: auto; + margin-top: auto; + border: 10px double rgb(64, 34, 2); + border-radius: 8px; + text-align: center; + padding: 10px 10px 10px 10px; + float: left; +} +.all-reviews-container p { + display: flex; + flex-direction: row; +} +.review-deets { + border: 10px double rgb(64, 34, 2); + padding-top: 20px; + padding-bottom: 20px; + margin-bottom: 2em; +} + +.plain-font { + font-family: sans-serif; +} +.average-rating-score { + border-top: 5px double rgb(64, 34, 2); + border-bottom: 5px double rgb(64, 34, 2); + margin-left: auto; + margin-right: auto; + padding-bottom: 20px; + padding-top: 20px; +} + +table { + width: 100%; + border-collapse: collapse; +} +td, th { + padding: 6px; + text-align: left; +} +@media +only screen and (max-width: 760px), +(min-device-width: 768px) and (max-device-width: 1024px) { + table, thead, tbody, th, td, tr { + display: block; + } +} +thead tr { + top: -9999px; + left: -9999px; +} + +tr { border: 1px solid #ccc; } + +td { + /* Behave like a "row" */ + border: none; + border-bottom: 1px solid #eee; + position: relative; + padding-left: 50%; +} + +td:before { + /* Now like a table header */ + position: absolute; + /* Top/left values mimic padding */ + top: 6px; + left: 6px; + width: 45%; + padding-right: 10px; + white-space: nowrap; +} 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..7bef9cf826 --- /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/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..b380c8c58d --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,12 @@ +class ApplicationController < ActionController::Base + protect_from_forgery with: :exception + + private + def find_merchant + if session[:merchant_id] + @login_merchant = Merchant.find_by(id: session[:merchant_id]) + + end + end + +end diff --git a/app/controllers/cartitems_controller.rb b/app/controllers/cartitems_controller.rb new file mode 100644 index 0000000000..17a09989d0 --- /dev/null +++ b/app/controllers/cartitems_controller.rb @@ -0,0 +1,78 @@ +class CartitemsController < ApplicationController + before_action :find_cartitem, only: [:show, :update, :destroy] + before_action :cartitem_params, only: [:update, :create] + + def create + if session[:cart_id] + @cart = Cart.find_by(id: session[:cart_id]) + @cartitem = Cartitem.new(cartitem_params) + @cartitem.cart_id = session[:cart_id] + if @cartitem.save + flash[:status] = :success + flash[:result_text] = "Your item has been added to your shopping cart" + return redirect_to products_path + else + if @cart.cartitems.find_by(product_id: @cartitem.product_id) + flash[:messages] = @cartitem.errors.messages + return redirect_to cart_path(@cart) + end + flash[:result_text] = "It was not possible to add this product to the cart" + flash[:messages] = @cartitem.errors.messages + end + else + @cart = Cart.new + if @cart.save + session[:cart_id] = @cart.id + @cartitem = Cartitem.new(cartitem_params) + @cartitem.cart_id = session[:cart_id] + if @cartitem.save + flash[:status] = :success + flash[:result_text] = "This product has been added to your shopping cart" + return redirect_to products_path + else + flash[:result_text] = "It was not possible to add this product to the cart" + end + end + end + flash[:status] = :failure + redirect_back(fallback_location: products_path) + end + + + def update + @cartitem.assign_attributes(cartitem_params) + if @cartitem.product.available?(params[:cartitem][:quantity].to_i) + if @cartitem.save + flash[:status] = :success + flash[:result_text] = "Successfully updated your #{@cartitem.product.name} quantity" + else + flash[:status] = :failure + flash[:result_text] = "Could not update your #{@cartitem.product.name} quantity" + flash[:messages] = @cartitem.errors.messages + end + else + flash[:status] = :failure + flash[:result_text] = "There is not enough #{@cartitem.product.name} in stock" + end + + redirect_to cart_path(@cartitem.cart_id) + end + + def destroy + @cartitem.destroy + flash[:status] = :success + flash[:result_text] = "Successfully removed #{@cartitem.product.name} from cart" + redirect_to cart_path(@cartitem.cart_id) + end + + private + def find_cartitem + @cartitem = Cartitem.find_by(id: params[:id]) + head :not_found unless @cartitem + end + + def cartitem_params + params.require(:cartitem).permit(:quantity, :product_id) + end + +end diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb new file mode 100644 index 0000000000..c1127e213c --- /dev/null +++ b/app/controllers/carts_controller.rb @@ -0,0 +1,28 @@ +class CartsController < ApplicationController + + before_action :find_cart, only: [:show, :empty_cart] + + def show; end + + def empty_cart + if @cart.id == session[:cart_id] + @cart.cartitems.destroy_all + + if @cart.total_items == 0 + flash[:success] + flash[:result_text] = "Your cart has been emptied" + end + else + flash[:status] = :failure + flash[:result_text] = "You can could not empty other people's cart" + flash[:messages] = @cart.errors.messages + end + redirect_back(fallback_location: cart_path(@cart)) + end + + private + def find_cart + @cart = Cart.find_by(id: params[:id]) + head :not_found unless @cart + end +end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb new file mode 100644 index 0000000000..1dc9933fc8 --- /dev/null +++ b/app/controllers/categories_controller.rb @@ -0,0 +1,41 @@ +class CategoriesController < ApplicationController + before_action :find_category, only: [:show] + def index + @categories = Category.all.paginate(:page => params[:page], :per_page => 3) + end + + def new + find_merchant + if params[:merchant_id] + @category = Category.new + end + end + + def show; end + + def create + @category = Category.new(category_params) + + if @category.save + flash[:status] = :success + flash[:result_text] = "Category added successfully" + redirect_to new_merchant_product_path + else + flash.now[:status] = :failure + flash.now[:result_text] = "Failed to add new category" + flash.now[:messages] = @category.errors.messages + render :new, status: :bad_request + end + end + + private + def category_params + return params.require(:category).permit(:name) + end + + def find_category + @category = Category.find_by(id: params[:id]) + head :not_found unless @category + 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/merchants_controller.rb b/app/controllers/merchants_controller.rb new file mode 100644 index 0000000000..9fff835166 --- /dev/null +++ b/app/controllers/merchants_controller.rb @@ -0,0 +1,38 @@ +class MerchantsController < ApplicationController + before_action :find_merchant, only: [:show, :show_products] + + def index + @merchants = Merchant.all.paginate(:page => params[:page], :per_page => 3) + end + + def show + if session[:merchant_id].to_s == params[:id] + @orders = @merchant.my_orders + else + flash[:status] = :failure + flash[:result_text] = "You can not view other merchant's account page" + redirect_back fallback_location: root_path + end + end + + def display + @merchant = Merchant.find_by(id: params[:merchant_id]) + end + + def show_products + @products = Product.where(merchant_id: params[:id]) + end + + def by_name + merchant_name = params[:username] + @merchants = Merchant.where("LOWER(username) like ?", "%#{merchant_name.downcase}%") + + render :index + end + + private + def find_merchant + @merchant = Merchant.find_by(id: params[:id]) + head :not_found unless @merchant + end +end diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 0000000000..2cfa87a739 --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,113 @@ +class OrdersController < ApplicationController + + before_action :find_order, only: [:update, :edit, :show, :my_order, :change_status] + + before_action :find_cart_by_session, only: [:new] + before_action :find_cart_by_order, only: [:update, :show] + + + def new + @order = Order.new + end + + def create + @order = Order.new(order_params) + @order.cart_id = session[:cart_id] + + if @order.save + flash[:status] = :success + redirect_to edit_order_path(@order) + else + flash.now[:status] = :failure + flash.now[:result_text] = "Could not place your order" + flash.now[:messages] = @order.errors.messages + + render :new, status: :bad_request + end + end + + def show; end + + def edit; end + + def update + @order.assign_attributes(order_params) + + if @order.save + @cart.cartitems.each do |cartitem| + product = Product.find(cartitem.product_id) + product.stock = product.new_stock(cartitem.quantity) + product.visible = false if product.stock == 0 + unless product.save + flash.now[:status] = :failure + flash.now[:result_text] = "Our #{product.name} is out of stock, please update your cart!" + redirect_to cart_path(@cart) + end + end + + flash[:status] = :success + flash[:result_text] = "Your order has been placed !" + flash[:order_number] = "Order number: #{@order.id}" + session[:cart_id] = nil + redirect_to order_path(@order) + else + + flash.now[:status] = :failure + flash.now[:result_text] = "Could not place your order" + flash.now[:messages] = @order.errors.messages + + render :edit, status: :bad_request + end + end + + def change_status + @order.assign_attributes(order_params) + + if @order.save + flash[:status] = :success + flash[:result_text] = "Status updated !" + flash[:order_number] = "Current status: #{@order.status}" + redirect_back fallback_location: order_path(@order) + else + flash.now[:status] = :failure + flash.now[:result_text] = "Could not update status" + flash.now[:messages] = @order.errors.messages + + render :edit, status: :bad_request + end + end + + def my_orders + @orders = [] + Order.all.each do |order| + order.cart.products.each do |product| + if product.merchant_id == session[:merchant_id] + @orders << order unless @orders.include?(order) + end + end + end + end + + def my_order; end + + private + def order_params + params.require(:order).permit(:name, :email, :creditcard, :name_on_card, :expiration_month, :expiration_year, :cvv, :mail_address, :billing_address, :zipcode, :status) + end + + def find_order + @order = Order.find_by(id: params[:id]) + head :not_found unless @order + end + + def find_cart_by_order + order = Order.find_by(id: params[:id]) + @cart = Cart.find_by(id: order.cart_id) + head :not_found unless @cart + end + + def find_cart_by_session + @cart = Cart.find_by(id: session[:cart_id]) + head :not_found unless @cart + end +end diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 0000000000..412846ffc0 --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,128 @@ +class ProductsController < ApplicationController + before_action :find_product, only: [:show, :edit, :update, :retire, :destroy] + + def homepage;end + + def index + @products = Product.where(visible: true).paginate(:page => params[:page], :per_page => 5) + end + + + def new + if session[:merchant_id] + @product = Product.new(merchant_id: params[:merchant_id]) + else + flash[:message] = "You must log in to add a product" + redirect_back fallback_location: products_path + end + end + + def create + if session[:merchant_id] + @product = Product.new(product_params) + if @product.save + flash[:status] = :success + flash[:result_text] = "Product added successfully" + redirect_to product_path(@product) + else + flash.now[:status] = :failure + flash.now[:result_text] = "Failed to add new product" + flash.now[:messages] = @product.errors.messages + render :new, status: :bad_request + end + else + flash[:status] = :failure + flash[:result_text] = "You must be logged in to do that" + redirect_back fallback_location: products_path + end + end + + def show + @cartitem = Cartitem.new + @review = Review.new + end + + def edit + if session[:merchant_id] + @product = Product.find(params[:id]) + else + flash[:status] = :failure + flash[:result_text] = "You must be logged in to do that" + redirect_to products_path + return + end + end + + def update + if session[:merchant_id] + @product.merchant_id = session[:merchant_id] + @product.assign_attributes(product_params) + if @product.save + redirect_to product_path(@product) + else + render :edit, status: :bad_request + end + else + flash[:status] = :failure + flash[:result_text] = "You must be logged in to do that" + redirect_to products_path, status: :unauthorized + return + end + end + + + def retire + if session[:merchant_id] + # retires a product that still has stock + if @product.visible == true + @product.update(visible: false) + flash[:status] = :success + flash[:result_text] = "Successfully retire #{@product.name}" + elsif + # can unretire an item as long as the stock is NOT 0 + @product.visible == false && @product.stock != 0 + @product.update(visible: true) + flash[:status] = :success + flash[:result_text] = "Successfully unretire #{@product.name}" + elsif + # retires a product if the stock drops to 0 + @product.visible == true && @product.stock == 0 + @product.update(visible: false) + flash[:status] = :failure + flash[:result_text] = "All out of #{@product.name}." + else + # will not retire an already retired item + flash[:status] = :failure + flash[:result_text] = "#{@product.name} has already retired." + end + else + # fails to retire an item + flash[:status] = :failure + if @product.visible == true + flash[:result_text] = "Failed to retire #{@product.name}" + else + # fails to unretire an item + flash[:result_text] = "Failed to unretire #{@product.name}" + end + end + redirect_back fallback_location: root_path + end + + def by_name + product_name = params[:name] + @products = Product.where(visible: true).where("LOWER(name) like ?", "%#{product_name.downcase}%").paginate(:page => params[:page], :per_page => 5) + + render :index + + end + + private + def product_params + params.require(:product).permit(:name, :price, :merchant_id, :description, :image, :stock, :visible, category_ids: []) + end + + def find_product + @product = Product.find_by(id: params[:id]) + head :not_found unless @product + end +end diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb new file mode 100644 index 0000000000..bc00fae6e5 --- /dev/null +++ b/app/controllers/reviews_controller.rb @@ -0,0 +1,38 @@ +class ReviewsController < ApplicationController + before_action :find_product, only: [:create] + + def new + @review = Review.new + end + + def create + @review = Review.new(review_params) + + if @product.merchant_id == session[:merchant_id] + flash[:status] = :failure + flash[:result_text] = "You may not review your own product" + redirect_to product_path(@review.product_id), status: :forbidden + else + if @review.save + flash[:status] = :success + flash[:result_text] = "Successfully created a review" + redirect_to product_path(@review.product_id) + else + flash[:status] = :failure + flash[:result_text] = "Could not create a review" + flash[:messages] = @review.errors.messages + redirect_back fallback_location: products_path, status: :bad_request + end + end + end + + private + def review_params + return params.require(:review).permit(:rating, :comment, :product_id) + end + + def find_product + @product = Product.find_by(id: params[:review][:product_id]) + head :not_found unless @product + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..1fb3a78ca9 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,45 @@ +class SessionsController < ApplicationController + + def login + auth_hash = request.env["omniauth.auth"] + if auth_hash["uid"] + @merchant = Merchant.find_by(uid: auth_hash["uid"], provider: auth_hash["provider"]) + + if @merchant.nil? + @merchant = Merchant.build_from_github(auth_hash) + if @merchant.id + flash[:status] = :success + flash[:result_text] = "#{@merchant.username} successfully logged in as a new merchant" + else + flash[:status] = :failure + flash[:result_text] = @merchant.errors.messages + end + else + flash[:status] = :success + flash[:result_text] = "#{@merchant.username} successfully logged in as existing merchant" + end + session[:merchant_id] = @merchant.id + + else + flash[:error] = "Could not log in" + end + + redirect_to root_path + end + + def logout + if session[:merchant_id] + @login_merchant = Merchant.find_by(id: session[:merchant_id]) + if session[:merchant_id] == @login_merchant.id + session[:merchant_id] = nil + flash[:status] = :success + flash[:result_text] = "Successfully logged out" + end + else + flash[:status] = :failure + flash[:result_text] = "You must login in" + end + redirect_to root_path + end + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..5ade132bf2 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,14 @@ +module ApplicationHelper + def display_dollars(cents) + dollars = cents / 100.0 + return '%.2f' % dollars + end + + def display_date(input) + return input.strftime("%B %d, %Y") + end + + def display_date_time(input) + return input.strftime("%m/%d/%Y at %I:%M%p") + end +end diff --git a/app/helpers/cartitems_helper.rb b/app/helpers/cartitems_helper.rb new file mode 100644 index 0000000000..ae03e13635 --- /dev/null +++ b/app/helpers/cartitems_helper.rb @@ -0,0 +1,2 @@ +module CartitemsHelper +end diff --git a/app/helpers/carts_helper.rb b/app/helpers/carts_helper.rb new file mode 100644 index 0000000000..d99c380cb5 --- /dev/null +++ b/app/helpers/carts_helper.rb @@ -0,0 +1,2 @@ +module CartsHelper +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/merchants_helper.rb b/app/helpers/merchants_helper.rb new file mode 100644 index 0000000000..5337747b0f --- /dev/null +++ b/app/helpers/merchants_helper.rb @@ -0,0 +1,2 @@ +module MerchantsHelper +end diff --git a/app/helpers/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..4c038f2d60 --- /dev/null +++ b/app/helpers/reviews_helper.rb @@ -0,0 +1,9 @@ +module ReviewsHelper + def stars (rating_num) + stars = " " + rating_num.to_i.times do + stars += "🍰" + end + return stars + end +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/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/cart.rb b/app/models/cart.rb new file mode 100644 index 0000000000..1341f8aa78 --- /dev/null +++ b/app/models/cart.rb @@ -0,0 +1,34 @@ +class Cart < ApplicationRecord + has_many :cartitems + has_many :products, through: :cartitems + has_one :order + + TAXRATE = 0.10 + + def subtotal + subtotal = 0 + self.cartitems.each do |cartitem| + subtotal += cartitem.subtotal + end + # self.cartitems.inject {|total, cartitem.subtotal| total + subtotal} + return subtotal + end + + def total_items + total_items = 0 + self.cartitems.each do |item| + total_items += item.quantity + end + return total_items + end + + def tax + tax = self.subtotal * TAXRATE + return tax + end + + def total_cost + total_cost = self.subtotal + self.tax + return total_cost + end +end diff --git a/app/models/cartitem.rb b/app/models/cartitem.rb new file mode 100644 index 0000000000..c7f591934d --- /dev/null +++ b/app/models/cartitem.rb @@ -0,0 +1,14 @@ +class Cartitem < ApplicationRecord + belongs_to :product + belongs_to :cart + + validates :quantity, presence: true, numericality: {only_integer: true, greater_than: 0} + validates :cart_id, uniqueness: { scope: :product_id, message: "This item is already in your cart, please update quantity in cart." } + + def subtotal + unit_price = self.product.price + subtotal = unit_price * self.quantity + return subtotal + end + +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000000..7019ddecbf --- /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 + + def show_four + if self.products.count > 4 + return self.products.first(4) + end + return self.products + 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/merchant.rb b/app/models/merchant.rb new file mode 100644 index 0000000000..15e5e65427 --- /dev/null +++ b/app/models/merchant.rb @@ -0,0 +1,84 @@ +class Merchant < ApplicationRecord + has_many :products + + validates :username, presence: true, uniqueness: true + validates :email, presence: true, uniqueness: true + + def self.build_from_github(auth_hash) + merchant = Merchant.new( + username: auth_hash[:info][:nickname], + email: auth_hash[:info][:email], + uid: auth_hash[:uid], + provider: auth_hash[:provider] + ) + merchant.save + return merchant + end + + def show_four + if self.visible_products.count > 4 + return self.visible_products.first(4) + end + return self.visible_products + end + + def visible_products + return self.products.reject{ |pro| pro.visible == false } + end + + def invisible_products + return self.products.reject{ |pro| pro.visible == true} + end + + def my_cartitems + my_cartitems = [] + Cartitem.all.each do |cartitem| + if self.products.include?(cartitem.product) + my_cartitems << cartitem + end + end + return my_cartitems + end + + def my_orders + my_orders = [] + Order.all.each do |order| + order.cart.cartitems.each do |cartitem| + if self.products.include?(cartitem.product) + my_orders << order + end + end + end + my_orders.uniq! + return my_orders + end + + def my_total_revenue + return 0 if self.my_cartitems.empty? + total_revenue = 0 + my_orders.each do |order| + order.cart.cartitems.each do |item| + if self.products.include?(item.product) + revenue = item.product.price * item.quantity + total_revenue += revenue + end + end + end + return total_revenue + end + + def my_revenue_by_status(status) + return 0 if self.my_cartitems.empty? + total_revenue = 0 + orders_by_status = my_orders.select { |order| order.status.downcase == status } + orders_by_status.each do |order| + order.cart.cartitems.each do |item| + if self.products.include?(item.product) + revenue = item.product.price * item.quantity + total_revenue += revenue + end + end + end + return total_revenue + end +end diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 0000000000..760f411dc5 --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,33 @@ +class Order < ApplicationRecord + belongs_to :cart + + validates :name, presence: true, unless: :is_pending + validates :email, presence: true, unless: :is_pending + validates :creditcard, presence: true, length: {is: 16}, unless: :is_pending + validates :expiration_month, presence: true, unless: :is_pending + validates :expiration_year, presence: true, unless: :is_pending + validates :name_on_card, presence: true, unless: :is_pending + validates :cvv, presence: true, length: {minimum: 3, maximum: 4}, unless: :is_pending + validates :mail_address, presence: true, unless: :is_pending + validates :billing_address, presence: true, unless: :is_pending + validates :status, presence: true + validates :zipcode, presence: true, unless: :is_pending + + + def is_pending + return self.status == "pending" + end + + TAXRATE = 0.10 + def order_tax + cart = Cart.find(self.cart_id) + tax = cart.subtotal * TAXRATE + return tax + end +#Sorry it is all double but need to dry it + def total + cart = Cart.find(self.cart_id) + total = (cart.subtotal * TAXRATE) + cart.subtotal + return total + end +end diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 0000000000..6788ecf417 --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,31 @@ +class Product < ApplicationRecord + has_and_belongs_to_many :categories + has_many :cartitems, dependent: :restrict_with_error + has_many :reviews, dependent: :destroy + # Included dependent destroy till we decide if we want these permanantly deleted -ALS + belongs_to :merchant + + validates :name, presence: true + validates :price, presence: true, numericality: {only_integer: true, greater_than: 0} + + def available?(new_quantity) + return self.stock >= new_quantity + end + + def average_rating + return nil unless self.reviews.any? + + total = 0 + self.reviews.each do |review| + total += review.rating + end + average_rating = total / self.reviews.count + return average_rating + end + + def new_stock(items_bought) + new_total = self.stock - items_bought + return new_total + end + +end diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000000..1c38def4fd --- /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, greater_than: 0, less_than: 6} +end diff --git a/app/views/carts/show.html.erb b/app/views/carts/show.html.erb new file mode 100644 index 0000000000..df4b6f9c49 --- /dev/null +++ b/app/views/carts/show.html.erb @@ -0,0 +1,47 @@ +
+ +

My Cart

+ +

Your cart includes <%= @cart.total_items %> delicious items.

+ + + + + + + + + + + + + <% @cart.cartitems.each do |item| %> + + + + + + + + <% end %> + + + + + + + + + +
Delicious Baked GoodnessQuantityUnit PriceSubtotal
<%= link_to "#{item.product.name}", product_path(item.product_id) %> + <%= form_for item do |f| %> + <%= f.number_field(:quantity, in: 1..item.product.stock) %> + <%= f.submit("Update Quantity") %> + <% end %> + <%= link_to("Remove this item", cartitem_path(item), method: :delete, data: { confirm: "Are you sure you want to delete #{item.product.name} from your cart?" }) %><%= display_dollars(item.product.price) %><%= display_dollars(item.subtotal) %>
Subtotal:<%= display_dollars(@cart.subtotal) %>
+ +<%= link_to "Continue Shopping", products_path, class: "button double-button" %> +<%= link_to "Empty Cart", empty_cart_path(@cart), method: :patch, data: { confirm: "Are you sure you want to empty your entire cart?" }, class: "button double-button" %> +<%= link_to "Proceed to Checkout", new_order_path, class: "button double-button" %> + +
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb new file mode 100644 index 0000000000..33a628640e --- /dev/null +++ b/app/views/categories/_form.html.erb @@ -0,0 +1,13 @@ +
+ <%= form_for @category, url: "/merchants/:merchant_id/categories" do |f| %> + + <%= f.label :name %> + <%= f.text_field :name %> + + <%= f.submit "Create a new category", class: "button double-button" %> + + <%= link_to "Back to my account", merchant_path(session[:merchant_id]), class: "button float-right double-button" %> + + <% end %> + +
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb new file mode 100644 index 0000000000..a2aed08562 --- /dev/null +++ b/app/views/categories/index.html.erb @@ -0,0 +1,16 @@ +
+ <% @categories.each do |cate| %> +

+ <%= link_to cate.name, category_path(cate) %> +

+ + <% end %> +
+<%= will_paginate @products %> diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb new file mode 100644 index 0000000000..5c8293f1ab --- /dev/null +++ b/app/views/categories/new.html.erb @@ -0,0 +1,3 @@ +
+ <%= render partial: "form", locals: { page_title: "Create a new category"} %> +
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb new file mode 100644 index 0000000000..9cfd211167 --- /dev/null +++ b/app/views/categories/show.html.erb @@ -0,0 +1,21 @@ +
+ +

+ <%= @category.name %> +

+
+ <% @category.products.each do |pro| %> +
+ <%= link_to image_tag(pro.image, alt: "middle-pro", class: "product-cat-img"), product_path(pro) %> +
+
    +
  • <%= link_to pro.name, product_path(pro) %>
  • +
  • Price: $<%= display_dollars(pro.price) %>
  • +
  • Seller: <%= link_to pro.merchant.username, merchant_path(pro.merchant)%>
  • +
  • Details: <%= pro.description %>
  • +
+
+
+
+ <% end %> +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..497764d9eb --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,122 @@ + + + + + + + <%= favicon_link_tag asset_path('cake.png'), :rel => 'icon', :type => 'image/png' %> + Ali-Bon-Bon + + + <%= stylesheet_link_tag "application" %> + <%= javascript_include_tag "application", 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> + +
+ +
+ + +
+
+

+ <%= link_to "Ali-Bon-Bon : Treat Yo Self!", root_path %> +

+
+ + +
+ + <% if flash[:result_text] or flash[:messages] %> +
+

<%= flash[:status] == :failure ? "A problem occurred: " : "" %><%= flash[:result_text] %>

+ <% if flash[:messages] %> + + <% 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/merchants/_order_table.html.erb b/app/views/merchants/_order_table.html.erb new file mode 100644 index 0000000000..b077ca8114 --- /dev/null +++ b/app/views/merchants/_order_table.html.erb @@ -0,0 +1,30 @@ + + + + + + + + + + + + <% data.each do |d| %> + + + + + <% if d.visible == true %> + + + <% else %> + + <% end %> + + + + <% end %> + +
Product IDNameStockStatusEdit
<%= d.id %><%= link_to d.name, product_path(d) %><%= d.stock %><%= link_to "Retire", retire_path(d), method: :patch, class: "button" %> + <%= link_to "Unretire", retire_path(d), method: :patch, class: "button" %> + <%= link_to "Edit", edit_product_path(d.id), class: "button" %>
diff --git a/app/views/merchants/display.html.erb b/app/views/merchants/display.html.erb new file mode 100644 index 0000000000..c69d680e87 --- /dev/null +++ b/app/views/merchants/display.html.erb @@ -0,0 +1,21 @@ +
+ +

View all products

+ + <%= link_to "Add a new product", new_merchant_product_path(@merchant), class: "button float-right double-button" %> + + <%= link_to "Back to my account", merchant_path(@merchant), class: "button float-right double-button" %> + +

Available Products

+ <%= render partial: "order_table", locals: { + data: @merchant.visible_products + } + %> + +

Retired Products

+ <%= render partial: "order_table", locals: { + data: @merchant.invisible_products + } + %> + +
diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb new file mode 100644 index 0000000000..fccb0e7607 --- /dev/null +++ b/app/views/merchants/index.html.erb @@ -0,0 +1,24 @@ +
+ + <%= form_tag merchant_by_name_path, method: :get, id: "search-bar-2" do %> + <%= text_field_tag :username, params[:username], required: true, id: "search-field-2"%> + <%= button_tag "Search Baker", class: 'button is-primary is-outlined is-inverted is-small', id: "search-button-2" %> + <% end %> + + <% @merchants.each do |merchant| %> +

+ <%= link_to merchant.username, show_products_path(merchant.id) %> +

+ + <% end %> +
+<%if @merchants.count > 4%> +<%= will_paginate @merchants %> +<%end%> diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb new file mode 100644 index 0000000000..76773e5914 --- /dev/null +++ b/app/views/merchants/show.html.erb @@ -0,0 +1,44 @@ +
+ + + <%= link_to "View all my products", display_path(@merchant), class: "button" %> + + <%= link_to "View all orders", my_orders_path, class: "button" %> + +

My Revenue

+ +
+
+

$ <%= display_dollars(@merchant.my_total_revenue) %>

+
+
+

$ <%= display_dollars(@merchant.my_revenue_by_status("pending")) %>

+
+
+

$ <%= display_dollars(@merchant.my_revenue_by_status("paid")) %>

+
+
+

$ <%= display_dollars(@merchant.my_revenue_by_status("completed")) %>

+
+
+

$ <%= display_dollars(@merchant.my_revenue_by_status("cancelled")) %>

+
+
+ +
diff --git a/app/views/merchants/show_products.html.erb b/app/views/merchants/show_products.html.erb new file mode 100644 index 0000000000..c653207fba --- /dev/null +++ b/app/views/merchants/show_products.html.erb @@ -0,0 +1,20 @@ +
+

+ <%= @merchant.username %> +

+
+ <% @products.each do |pro| %> +
+ <%= link_to image_tag(pro.image, alt: "middle-pro", class: "product-cat-img"), product_path(pro) %> +
+
    +
  • <%= link_to pro.name, product_path(pro) %>
  • +
  • Price: $<%= display_dollars(pro.price) %>
  • +
  • Details: <%= pro.description %>
  • +
+
+
+ <% end %> + +
+
diff --git a/app/views/orders/_form.html.erb b/app/views/orders/_form.html.erb new file mode 100644 index 0000000000..1626881a12 --- /dev/null +++ b/app/views/orders/_form.html.erb @@ -0,0 +1,44 @@ +
+ + <%= form_for @order do |t|%> + +

Personal information:

+ <%= t.label :name %> + <%= t.text_field :name%> + + <%= t.label :email %> + <%= t.email_field :email%> + +

Payment information:

+ <%= t.label :name_on_card%> + <%= t.text_field :name_on_card%> + + <%= t.label :creditcard%> + <%= t.number_field :creditcard%> + + <%= t.label :expiration_month%> + <%= t.date_select :expiration_month, :discard_day => true, :discard_year => true, :use_month_numbers => true %> + + <%= t.label :expiration_year%> + <%= t.date_select :expiration_year, :discard_day => true, :start_year => Date.today.year, :end_year => (Date.today.year+10), :discard_month => true%> + + <%= t.label :cvv%> + <%= t.number_field :cvv%> + + <%= t.label :billing_address%> + <%= t.text_field :billing_address%> + +

Delivery information:

+ <%= t.label :mail_address%> + <%= t.text_field :mail_address%> + + <%= t.label :zipcode%> + <%= t.number_field :zipcode%> + + <%= t.hidden_field :status, value: "paid" %> +
+ <%= t.submit "Place Order", class: "button hollow double-button"%> +
+ <%end%> + +
diff --git a/app/views/orders/_order_items_review.html.erb b/app/views/orders/_order_items_review.html.erb new file mode 100644 index 0000000000..10710e1266 --- /dev/null +++ b/app/views/orders/_order_items_review.html.erb @@ -0,0 +1,26 @@ +
+ + + + + + + + + + + + + + <% @cart.cartitems.each do |item| %> + + + + + + + + <% end %> + +

Delicious Baked Goodness

Quantity

Unit Price

Subtotal

<%= link_to "#{item.product.name}", product_path(item.product_id) %><%= item.quantity %><%= display_dollars(item.product.price) %><%= display_dollars(item.subtotal) %>
+
diff --git a/app/views/orders/edit.html.erb b/app/views/orders/edit.html.erb new file mode 100644 index 0000000000..ca5adf16c3 --- /dev/null +++ b/app/views/orders/edit.html.erb @@ -0,0 +1,4 @@ +
+

Please add your Payment and billing info

+ <%= render partial: "form" %> +
diff --git a/app/views/orders/my_order.html.erb b/app/views/orders/my_order.html.erb new file mode 100644 index 0000000000..16e70a75e8 --- /dev/null +++ b/app/views/orders/my_order.html.erb @@ -0,0 +1,11 @@ +
+ +<%= link_to "Back to my order list", my_orders_path, class: "button float-right double-button" %> +<%= link_to "Back to my account", merchant_path(session[:merchant_id]), class: "button float-right double-button" %> +
diff --git a/app/views/orders/my_orders.html.erb b/app/views/orders/my_orders.html.erb new file mode 100644 index 0000000000..395ea1ae3f --- /dev/null +++ b/app/views/orders/my_orders.html.erb @@ -0,0 +1,59 @@ +
+ + <%= link_to "Back to my account", merchant_path(session[:merchant_id]), class: "button float-right double-button" %> + + + + + + + + + + + + + + + <% @orders.each do |order| %> + + + + + + + + <% end %> + +
+ ID + + Date + + My products + + Revenue + + Status +
+ <%= link_to order.id, my_order_path(order) %> + + <%= display_date(order.created_at) %> + + <% revenue = []%> + <% order.cart.cartitems.joins(:product).where(products: { merchant_id: session[:merchant_id]}).each do |cartitem|%> + <% revenue << (Product.find(cartitem.product_id).price * cartitem.quantity)%> +
    +
  • <%= Product.find(cartitem.product_id).name %>
  • + <%end%> +
+
$<%= display_dollars(revenue.sum)%> + <%= form_for order, url: change_status_path(order) do |f| %> + <%= f.label :status %> + <%= f.select :status, options_for_select([["#{order.status}",""],"Pending", "Paid", "Completed", "Cancelled"],:disabled => ["Pending"],:prompt => ["#{order.status}"] )%> + <%= f.submit "Change status"%> + <%end%> +
+ + +
diff --git a/app/views/orders/new.html.erb b/app/views/orders/new.html.erb new file mode 100644 index 0000000000..10fd9bdf9d --- /dev/null +++ b/app/views/orders/new.html.erb @@ -0,0 +1,30 @@ +
+ +

Order Review

+ + <%= render partial: "order_items_review" %> + + + + + + + + + + + + + +
Subtotal: $<%= display_dollars(@cart.subtotal)%>
Tax: $<%= display_dollars(@cart.tax)%>

Total: $<%= display_dollars(@cart.total_cost)%>

+ + +
+ <%= link_to "Edit shopping cart", cart_path(session[:cart_id]), class: "hollow button double-button"%> + <%= form_for @order do |t|%> + <%= t.hidden_field :status, value: "pending" %> + <%= t.submit "Proceed to payment", class: "hollow button double-button" %> + <%end%> +
+ +
diff --git a/app/views/orders/show.html.erb b/app/views/orders/show.html.erb new file mode 100644 index 0000000000..9bb25b3c0b --- /dev/null +++ b/app/views/orders/show.html.erb @@ -0,0 +1,7 @@ +
+

Order Number: <%= @order.id%>

+
+ <%= render partial: "order_items_review" %> +
+

Thank you for shopping at ALI-BON-BON

+
diff --git a/app/views/products/_form.html.erb b/app/views/products/_form.html.erb new file mode 100644 index 0000000000..9782024a55 --- /dev/null +++ b/app/views/products/_form.html.erb @@ -0,0 +1,38 @@ +
+

+ <% page_title ||= "Create a New Product"%> + <%= page_title %> +

+ + <%= form_for @product do |f| %> + <%= f.label :name %> + <%= f.text_field :name%> + + <%= f.hidden_field :merchant_id %> + +
+ <%= f.label :categories %> + <%= collection_check_boxes(:product, :category_ids, Category.all, :id, :name) %> + <%= link_to "Create a new category", new_merchant_category_path(session[:merchant_id]), class: "button double-button" %> +
+ + <%= f.label :price %> + <%= f.number_field :price %> + + <%= f.label :description %> + <%= f.text_area :description %> + + <%= f.label :stock %> + <%= f.number_field :stock %> + + <%= f.hidden_field :merchant_id, :value => params[:merchant_id] %> + + <%= f.label :image %> + <%= f.text_field :image %> + + <%= f.hidden_field :visible, :value => true %> + + <%= f.submit class: "button" %> + + <% end %> +
diff --git a/app/views/products/edit.html.erb b/app/views/products/edit.html.erb new file mode 100644 index 0000000000..aa07e349fb --- /dev/null +++ b/app/views/products/edit.html.erb @@ -0,0 +1,3 @@ +
+ <%= render partial: "form", locals: { page_title: "Edit your product"} %> +
diff --git a/app/views/products/homepage.html.erb b/app/views/products/homepage.html.erb new file mode 100644 index 0000000000..ce649d5782 --- /dev/null +++ b/app/views/products/homepage.html.erb @@ -0,0 +1,44 @@ + + +
+
+
+ + +
+ +
+ +
+ + diff --git a/app/views/products/index.html.erb b/app/views/products/index.html.erb new file mode 100644 index 0000000000..33f2ee4abb --- /dev/null +++ b/app/views/products/index.html.erb @@ -0,0 +1,36 @@ +

Delicious Products

+ +<%= form_tag product_by_name_path, method: :get, id: "search-bar" do %> + <%= text_field_tag :name, params[:name], required: true, id: "search-field"%> + <%= button_tag "Search Bonbon", class: "button is-primary is-outlined is-inverted is-small", id: "search-button" %> +<% end %> + + + + + + + + + + + + <% @products.each do |product| %> + + + + + + + <% end %> + +
OooooName:Sold By:Average Rating:
+ <%= link_to image_tag(product.image, alt: "bonbon-image", class: "product_pic"), product_path(product) %> + + <%= link_to product.name, product_path(product) %> + + <%= link_to product.merchant.username, show_products_path(product.merchant) %> + + <%= stars(product.average_rating) %> +
+<%= will_paginate(@products) %> diff --git a/app/views/products/new.html.erb b/app/views/products/new.html.erb new file mode 100644 index 0000000000..f6ecea6fb5 --- /dev/null +++ b/app/views/products/new.html.erb @@ -0,0 +1 @@ +<%= render partial: "form", locals: { page_title: "Create a new product"} %> diff --git a/app/views/products/show.html.erb b/app/views/products/show.html.erb new file mode 100644 index 0000000000..76a999d82e --- /dev/null +++ b/app/views/products/show.html.erb @@ -0,0 +1,56 @@ +

<%= @product.name %>

+ +
+ +
+
+ <%= image_tag(@product.image, alt: "bonbon-image", class: "bonbon-img") %> +
+
+ +
+

Details:

+

Seller: <%= link_to @product.merchant.username, show_products_path(@product.merchant) %>

+

Description: <%= @product.description %>

+

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

+

Stock: <%= @product.stock %>

+ <%= form_for @cartitem do |f|%> + <%= f.hidden_field :product_id, :value => @product.id %> + + <%= f.label :quantity %> + <%= f.number_field(:quantity, in: 1..@product.stock, value: 1) %> + <%= f.submit "Add to cart", :class => 'button' %> + <% end %> +
+ +
+

Write a Review

+ <%= form_for @review do |f| %> + + <%= f.hidden_field :product_id, :value => @product.id %> + + <%= f.label :rating %> + <%= f.number_field :rating %> + + <%= f.label :comment %> + <%= f.text_field :comment %> + + <%= f.submit class: "button" %> + <% end %> +
+
+ +
+
+

Reviews:

+

Average Rating: <%= stars @product.average_rating %>

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

"<%= review.comment %>"

+

Rating: <%= stars(review.rating) %>

+

Reviewed On: <%= display_date_time(review.created_at) %>

+
+ <% 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/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..9284f84839 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,91 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "betsy_#{Rails.env}" + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000000..8e5cbde533 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,42 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000000..89d2efab2b --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000..4b828e80cb --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000000..59385cdf37 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000000..5a6a32d371 --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..4a994e1e7b --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000000..ac033bf9dc --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000000..dc1899682b --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb new file mode 100644 index 0000000000..fd4416122a --- /dev/null +++ b/config/initializers/omniauth.rb @@ -0,0 +1,3 @@ +Rails.application.config.middleware.use OmniAuth::Builder do + provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], scope: "user:email" +end diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000000..bbfc3961bf --- /dev/null +++ b/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000000..decc5a8573 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,33 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000000..1e19380dcb --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,56 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end +# + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..3c07e6c2f7 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,40 @@ +Rails.application.routes.draw do + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + # resources :products, only: [:index, :show] + get 'merchants/by_name', to: 'merchants#by_name', as: 'merchant_by_name' + resources :merchants do + resources :products, only: [ :new, :create, :edit, :update] + end + + patch "/products/retire/:id", to: "products#retire", as: "retire" + get "/merchants/:merchant_id/display-products", to: "merchants#display", as: "display" + + get 'products/by_name', to: 'products#by_name', as: 'product_by_name' + resources :products, only: [:index, :edit, :show, :new, :create, :update] + resources :products, only: [:index, :show] + + + resources :merchants do + resources :categories, only: [:index, :new, :create, :show] + end + + resources :orders + get "/my_orders", to: "orders#my_orders", as: "my_orders" + get "/my_orders/:id", to: "orders#my_order", as: "my_order" + patch "/my_orders/:id/change_status" , to: "orders#change_status", as: "change_status" + + get "/merchants/:id/show_products", to: "merchants#show_products", as: "show_products" + + resources :categories, only: [:index, :edit, :show] + resources :carts, only: [:show] + resources :cartitems + resources :reviews, only: [:new, :create] + + root 'products#homepage' + + get "/auth/:provider/callback", to: "sessions#login", as: "auth_callback" + + delete "/logout", to: "sessions#logout", as: "logout" + + patch "/carts/:id/empty_cart", to: "carts#empty_cart", as: "empty_cart" +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000000..e685f384b5 --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,32 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: 583cd3b06203ec95d9991f2bea878969261d2a78c80459dae0a69ad97f7d811ab79467799f4f7380d61d0baacca8c846307aa999c5ce9097f7d0ad3cda609ae0 + +test: + secret_key_base: 2bb915214f7a8d7da9b50c89611d4b43b29c05a31dd03ade65cb607dd4ce14ba179663f24c6e8213c83ee4e68e7aaa39f9619f6999a93bd8e6d5bb16195fface + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 0000000000..c9119b40c0 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/db/migrate/20180419210532_create_products.rb b/db/migrate/20180419210532_create_products.rb new file mode 100644 index 0000000000..635506ccb9 --- /dev/null +++ b/db/migrate/20180419210532_create_products.rb @@ -0,0 +1,14 @@ +class CreateProducts < ActiveRecord::Migration[5.1] + def change + create_table :products do |t| + t.string :name + t.float :price + t.string :description + t.string :image + t.integer :stock + t.boolean :visible + + t.timestamps + end + end +end diff --git a/db/migrate/20180419211255_create_merchants.rb b/db/migrate/20180419211255_create_merchants.rb new file mode 100644 index 0000000000..41275d9ea0 --- /dev/null +++ b/db/migrate/20180419211255_create_merchants.rb @@ -0,0 +1,12 @@ +class CreateMerchants < ActiveRecord::Migration[5.1] + def change + create_table :merchants do |t| + t.string :username + t.string :email + t.string :uid + t.string :provider + + t.timestamps + end + end +end diff --git a/db/migrate/20180419211405_create_categories.rb b/db/migrate/20180419211405_create_categories.rb new file mode 100644 index 0000000000..5bef4913b8 --- /dev/null +++ b/db/migrate/20180419211405_create_categories.rb @@ -0,0 +1,9 @@ +class CreateCategories < ActiveRecord::Migration[5.1] + def change + create_table :categories do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/db/migrate/20180419211442_create_reviews.rb b/db/migrate/20180419211442_create_reviews.rb new file mode 100644 index 0000000000..f4e13a726d --- /dev/null +++ b/db/migrate/20180419211442_create_reviews.rb @@ -0,0 +1,10 @@ +class CreateReviews < ActiveRecord::Migration[5.1] + def change + create_table :reviews do |t| + t.integer :rating + t.string :comment + + t.timestamps + end + end +end diff --git a/db/migrate/20180419211658_create_carts.rb b/db/migrate/20180419211658_create_carts.rb new file mode 100644 index 0000000000..29c9a8bc85 --- /dev/null +++ b/db/migrate/20180419211658_create_carts.rb @@ -0,0 +1,8 @@ +class CreateCarts < ActiveRecord::Migration[5.1] + def change + create_table :carts do |t| + + t.timestamps + end + end +end diff --git a/db/migrate/20180419211734_create_cartitems.rb b/db/migrate/20180419211734_create_cartitems.rb new file mode 100644 index 0000000000..f3aba42945 --- /dev/null +++ b/db/migrate/20180419211734_create_cartitems.rb @@ -0,0 +1,9 @@ +class CreateCartitems < ActiveRecord::Migration[5.1] + def change + create_table :cartitems do |t| + t.integer :quantity + + t.timestamps + end + end +end diff --git a/db/migrate/20180419211908_create_orders.rb b/db/migrate/20180419211908_create_orders.rb new file mode 100644 index 0000000000..b3780ffad0 --- /dev/null +++ b/db/migrate/20180419211908_create_orders.rb @@ -0,0 +1,18 @@ +class CreateOrders < ActiveRecord::Migration[5.1] + def change + create_table :orders do |t| + t.string :name + t.string :email + t.string :creditcard + t.string :name_on_card + t.date :expiration_date + t.string :cvv + t.string :mail_address + t.string :billing_address + t.string :zipcode + t.string :status + + t.timestamps + end + end +end diff --git a/db/migrate/20180419214957_add_foreign_keys.rb b/db/migrate/20180419214957_add_foreign_keys.rb new file mode 100644 index 0000000000..443fa630f9 --- /dev/null +++ b/db/migrate/20180419214957_add_foreign_keys.rb @@ -0,0 +1,11 @@ +class AddForeignKeys < ActiveRecord::Migration[5.1] + def change + add_reference :products, :merchant, foreign_key: true + add_reference :cartitems, :product, foreign_key: true + add_reference :cartitems, :cart, foreign_key: true + add_reference :reviews, :product, foreign_key: true + add_reference :orders, :cart, foreign_key: true + + + end +end diff --git a/db/migrate/20180420175219_create_categories_products_join_table.rb b/db/migrate/20180420175219_create_categories_products_join_table.rb new file mode 100644 index 0000000000..30c328c6eb --- /dev/null +++ b/db/migrate/20180420175219_create_categories_products_join_table.rb @@ -0,0 +1,5 @@ +class CreateCategoriesProductsJoinTable < ActiveRecord::Migration[5.1] + def change + create_join_table :categories, :products + end +end diff --git a/db/migrate/20180420184759_change_product_price_to_integer.rb b/db/migrate/20180420184759_change_product_price_to_integer.rb new file mode 100644 index 0000000000..3598b89a53 --- /dev/null +++ b/db/migrate/20180420184759_change_product_price_to_integer.rb @@ -0,0 +1,5 @@ +class ChangeProductPriceToInteger < ActiveRecord::Migration[5.1] + def change + change_column :products, :price, :integer + end +end diff --git a/db/migrate/20180423191848_change_date_fields_for_order.rb b/db/migrate/20180423191848_change_date_fields_for_order.rb new file mode 100644 index 0000000000..751e886cb5 --- /dev/null +++ b/db/migrate/20180423191848_change_date_fields_for_order.rb @@ -0,0 +1,7 @@ +class ChangeDateFieldsForOrder < ActiveRecord::Migration[5.1] + def change + remove_column :orders, :expiration_date + add_column :orders, :expiration_month, :date + add_column :orders, :expiration_year, :date + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..01545a7ff0 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,98 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20180423191848) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "cartitems", force: :cascade do |t| + t.integer "quantity" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "product_id" + t.bigint "cart_id" + t.index ["cart_id"], name: "index_cartitems_on_cart_id" + t.index ["product_id"], name: "index_cartitems_on_product_id" + end + + create_table "carts", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "categories", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "categories_products", id: false, force: :cascade do |t| + t.bigint "category_id", null: false + t.bigint "product_id", null: false + end + + create_table "merchants", force: :cascade do |t| + t.string "username" + t.string "email" + t.string "uid" + t.string "provider" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "orders", force: :cascade do |t| + t.string "name" + t.string "email" + t.string "creditcard" + t.string "name_on_card" + t.string "cvv" + t.string "mail_address" + t.string "billing_address" + t.string "zipcode" + t.string "status" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "cart_id" + t.date "expiration_month" + t.date "expiration_year" + t.index ["cart_id"], name: "index_orders_on_cart_id" + end + + create_table "products", force: :cascade do |t| + t.string "name" + t.integer "price" + t.string "description" + t.string "image" + t.integer "stock" + t.boolean "visible" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "merchant_id" + t.index ["merchant_id"], name: "index_products_on_merchant_id" + end + + create_table "reviews", force: :cascade do |t| + t.integer "rating" + t.string "comment" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "product_id" + t.index ["product_id"], name: "index_reviews_on_product_id" + end + + add_foreign_key "cartitems", "carts" + add_foreign_key "cartitems", "products" + add_foreign_key "orders", "carts" + add_foreign_key "products", "merchants" + add_foreign_key "reviews", "products" +end diff --git a/db/seed_data/categories.csv b/db/seed_data/categories.csv new file mode 100644 index 0000000000..51272036a3 --- /dev/null +++ b/db/seed_data/categories.csv @@ -0,0 +1,13 @@ +id,name +1,Fruit +2,Chocolate +3,Milk based dessert +4,Flour free +5,Egg free +6,Sugar free +7,Vegan +8,Asian +9,South American +10,Breads +11,Pies +13,Cakes diff --git a/db/seed_data/merchants.csv b/db/seed_data/merchants.csv new file mode 100644 index 0000000000..4507d12419 --- /dev/null +++ b/db/seed_data/merchants.csv @@ -0,0 +1,6 @@ +id,username,email,uid,provider +1,DiklasHeaven,dikla@everybodylovesmyfood.com,89034889,github +2,AnaLisasExtravaganza,al@sweetbutnotsosweet.com,873203,github +3,CarolinesBreads,caroline@theonewhodoesnothavesweetstuff.com,97352,github +4,GetSurprisedByWenjie,wenjie@alwayswithsomethingnewtotry.com,73028455,github +5,Winislatinx,wini@dulcedelechemanjaralfajor.com,94723945,github diff --git a/db/seed_data/products.csv b/db/seed_data/products.csv new file mode 100644 index 0000000000..6aa791cd39 --- /dev/null +++ b/db/seed_data/products.csv @@ -0,0 +1,30 @@ +id,merchant_id,name,price,description,stock,image,visible,category + +1,2,Caroline's Angelic Key Lime Pie,2099,All ingredients are fair trade products,5,http://cdn-image.foodandwine.com/sites/default/files/styles/medium_2x/public/200212-r-xl-tangy-key-lime-pie.jpg?itok=x65bW9tP,true,1 +2,4,Brenda's Dreamy Strawberry Cheesecake,3350,Classic baked cheesecake with fresh strawberries in an almond murbe pastry base -- Allergies: nuts; eggs; flour; dairy,7,http://www.cbc.ca/inthekitchen/assets_c/2012/12/StrawberryCheescake1-thumb-596x350-252247.jpg,true,13 +3,3,Angelica's Granny Carrot Cake,1500,Secret family recipe coming all the way from Mexico -- Allergies: nuts; eggs; flour. DAIRY FREE OPTION,9,https://food.fnr.sndimg.com/content/dam/images/food/fullset/2014/3/3/1/FNK_Easter-Carrot-Cake-with-Cream-Cheese-Frosting_s4x3.jpg.rend.hgtvcom.616.462.suffix/1393906711447.jpeg,true,13 +4,5,Latinxs' and Maja's Favorite Tres Leches,1800,Heavenly moist cake with evaporated milk; condensed milk and cream covered with Italian meringue,4,https://www.browneyedbaker.com/wp-content/uploads/2016/05/tres-leches-cake-37-600-600x454.jpg,true,3 +5,1,Il Tiramisu di Maria,2100,Lady finger biscuits with Italian mascarpone and coldbrew,6,https://i.pinimg.com/736x/aa/6d/c4/aa6dc4714fabb248f8350c0cc075355f--laduree-paris-food-plating.jpg,true,13 +6,2,Kiera's Sweet Potato Pie,2050,Sweet Potatoes made ...,8,https://www.cscassets.com/recipes/wide_cknew/wide_27102.jpg,true,11 +7,4,Luxi's Asian Cake,3099,Angel cake covered with whipped cream and fruits,1,http://www.rgbstock.com/cache1vOIb4/users/c/co/coolhewitt23/300/opbWx9e.jpg,true,8 +8,2,Ana Lisa's Prize Winning Lemon Tart,2500, LOW SUGAR lemon Tart,8,https://i.ytimg.com/vi/E55gHIOXZEU/maxresdefault.jpg,true,1 +9,3,Pain au Chocolat,400,Each pack comes with 4,40,https://timedotcom.files.wordpress.com/2017/05/quanthem.jpg?quality=85,true,2 +10,4,Tor's Homemade Macaroons,1599,The most famous macaroons. Almond based biscuit; flour free; filled with a explotion of flavour. Each box comes with 6,36,http://wallaceyolicia.files.wordpress.com/2014/02/sweet-macaron-laduree.jpg,true,4 +11,2,Dikla's Challah,1000,Kosher homemade bread. GLUTEN FREE OPTION,15,https://static01.nyt.com/images/2015/11/19/dining/19COOKING-CHALLAHLOAF2/19COOKING-CHALLAHLOAF2-superJumbo.jpg,true,10 +12,5,Argentinian Quince Facturas,525,Quince and creme patissier filled puff pastries. ALLERGIES: DAIRY; EGGS; WHEAT.,30,http://upload.wikimedia.org/wikipedia/commons/7/7c/Factura_membrillo.jpg,true,9 +13,5,Alfajores,2500,Milk caramel filled cornflour pastry. ALLERGIES: DAIRY.,44,http://www.chowstatic.com/assets/2014/09/28373_alfajores_3000.jpg,true,9 +14,2,Bake At Home Apple Gallete,1200,Crisp crust filled with apples. ALLERGIES: DAIRY; WHEAT.,10,http://shaunasever.com/wpshauna/wp-content/uploads/image-import/_gv2ZOLiWKJg/S-dMoUD7o7I/AAAAAAAACcA/1T3YJB_11es/s1600/DSC08561.JPG,true,11 +15,4,Cara´s specialty: Frozen bananas and cacao,699,Simplest frozen bananas covered with brazilian cacao.,15,https://www.wickedspatula.com/wp-content/uploads/2015/06/Banana-Popsicles_-2.jpg,true,6 +16,4,Anne´s Delightful Orange Pushpops (8),1000,Natural orange juice sorbet,15,https://heatherchristo.com/wp-content/uploads/2014/05/Mango-Vanilla-Honey-Push-up-Pops.jpg,true,1 +17,1,Dan´s Spoonable Brownies (6),1550,Very moist and gooey brownie; perfect to eat with a spoon and ice cream.,20,http://myblessedlife.net/wp-content/uploads/2010/09/brownie.jpg,true,2 +18,2,Angela´s Cream Puff,1050,Choux pastry filled with Tahitian Vanilla cream patissier,5,http://1.bp.blogspot.com/-_gBCPKY2EKA/TwbZt4WxbUI/AAAAAAAAA-U/Yr53Pn9vXyk/s1600/Pai+Shu+Cream+Puffs+by+Little+Japan+Mama1.jpg,true,3 +19,5,Always Creme Brulle (broil at home),2000,The perfect Creme Brulee made with Tahitian vanilla and the most perfect caramel after 5 min in high broil,10,http://3.bp.blogspot.com/_hxA4j4yxueY/TJ67MEnS2qI/AAAAAAAAMzQ/ZTveppP2938/s1600/Creme+Brulee+027.JPG,true,3 +20,2,Hannah`s Guilty Pleasure Zebra Cake,500,,50,http://agentnerdy.files.wordpress.com/2010/10/tumblr_l1g17ysynh1qa5q3co1_500_large.jpg?w=400&h=266,true,3 +21,3,Ari´s Famous Sea Salt Caramel Bon-Bons,2500,Dark Belgian chocolate filled with sea salt caramel.,40,https://pixel.nymag.com/imgs/daily/grub/2015/01/23/stick-with-me/stick-with-me24.o.jpg/a_560x375.w710.h473.jpg,true,2 +22,4,Kat's Freakin´Awesome Raspery Sorbet,850,Handpick raspberries from a local farm made into a creamed textured sorbet. Allergies: EGGS.,30,https://tse3.mm.bing.net/th?id=OIP.3nvoJWpXC1I44GQr7FoPKgHaHa&pid=15.1&P=0&w=300&h=300,true,5 +23,1,Karina´s Gansito Cupcakes,1200,Vanilla cake filled with cream and strawberry jam convered with chocolate.,34,https://i.pinimg.com/originals/45/26/91/452691d75a9991ede4ab30c6b5c81097.jpg,true,9 +24,2,Jamila's New Generation Yogurt Pie,1500,The perfect yogurt pie. Made with local farm fresh products,6,https://images-gmi-pmc.edge-generalmills.com/3e38492d-1ec4-4cba-8903-758c7eb10a4c.jpg,true,11 +25,2,The Perfect Chocolate Truffles For Emilce,2050,The richest Belgian chocolate turned into a truffle,23,https://tse3.mm.bing.net/th?id=OIP.vgf5LoQURg0bpATtNQnvawHaF6&pid=15.1&P=0&w=190&h=153,true,2 +26,1,Kari´s French Fruit Tart,3099,Made with murbe pastry filled with Tahitian vanilla creme patissier and toped with your choice of fruit,7,https://tse2.mm.bing.net/th?id=OIP.QbENAABkPIiiXzPaLJbCCAHaE9&pid=15.1&P=0&w=246&h=166,true,11 +27,1,Brandy´s Creme Renversee au Caramel,1800,The perfect and classic french flan. ALLERGIES: EGGS.,5,http://www.femmezine.fr/files/images/article/82/4f1936a66cc82-304-large.jpg.true,3 +28,3,Sam´s Assorted Cake Pops,700,"Assorted vanilla, chocolate, and red velvet cake pops",23,http://static.wixstatic.com/media/06b7c5_4e862ade619949a29f25bd27c03dd36c.jpg_srz_1467_634_85_22_0.50_1.20_0.00_jpg_srz,true,7 diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..919391a1ff --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,87 @@ +require 'csv' + +MERCHANT_FILE = Rails.root.join('db', 'seed_data', 'merchants.csv') +puts "Loading raw merchant data from #{MERCHANT_FILE}" + +merchant_failures = [] +CSV.foreach(MERCHANT_FILE, :headers => true) do |row| + merchant = Merchant.new + merchant.id = row['id'] + merchant.username = row['username'] + merchant.email = row['email'] + merchant.uid = row['uid'] + merchant.provider = row['provider'] + successful = merchant.save + if !successful + merchant_failures << merchant + puts "Failed to save merchant: #{merchant.inspect}" + else + puts "Created merchant: #{merchant.inspect}" + end +end + +puts "Added #{Merchant.count} merchant records" +puts "#{merchant_failures.length} merchants failed to save" + +CATEGORY_FILE = Rails.root.join('db', 'seed_data', 'categories.csv') +puts "Loading raw category data from #{CATEGORY_FILE}" + +category_failures = [] +CSV.foreach(CATEGORY_FILE, :headers => true) do |row| + category = Category.new + category.id = row['id'] + category.name = row['name'] + + successful = category.save + if !successful + category_failures << category + puts "Failed to save category: #{category.inspect}" + else + puts "Created category: #{category.inspect}" + end +end + +puts "Added #{Category.count} category records" +puts "#{category_failures.length} categories failed to save" + +PRODUCT_FILE = Rails.root.join('db', 'seed_data', 'products.csv') +puts "Loading raw product data from #{PRODUCT_FILE}" + +product_failures = [] +CSV.foreach(PRODUCT_FILE, :headers => true) do |row| + product = Product.new + product.id = row['id'] + product.name = row['name'] + product.price = row['price'] + product.description = row['description'] + product.stock = row['stock'] + product.image = row['image'] + product.visible = row['visible'] + product.categories << Category.find(row['category']) if row['category'] + product.merchant_id = row['merchant_id'] + successful = product.save + if !successful + product_failures << product + puts "===========================================================Failed to save product: #{product.inspect}===============" + else + puts "Created product: #{product.inspect}" + end +end + +puts "Added #{Product.count} product records" +puts "#{product_failures.length} products failed to save" + + + + + +# Since we set the primary key (the ID) manually on each of the +# tables, we've got to tell postgres to reload the latest ID +# values. Otherwise when we create a new record it will try +# to start at ID 1, which will be a conflict. +puts "Manually resetting PK sequence on each table" +ActiveRecord::Base.connection.tables.each do |t| + ActiveRecord::Base.connection.reset_pk_sequence!(t) +end + +puts "done" diff --git a/lib/assets/.keep b/lib/assets/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..f874acf437 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "betsy", + "private": true, + "dependencies": {} +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000..2be3af26fc --- /dev/null +++ b/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

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

+
+

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

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

The change you wanted was rejected.

+

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

+
+

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

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

We're sorry, but something went wrong.

+
+

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

+
+ + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..37b576a4a0 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000000..d19212abd5 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/controllers/cartitems_controller_test.rb b/test/controllers/cartitems_controller_test.rb new file mode 100644 index 0000000000..5c839c3dd1 --- /dev/null +++ b/test/controllers/cartitems_controller_test.rb @@ -0,0 +1,147 @@ +require "test_helper" + +describe CartitemsController do + + describe "create" do + it "should create a cartitem when a cart does not exit" do + old_cartitem_count = Cartitem.count + data = { + product_id: Product.first.id, + quantity: 3 + } + post cartitems_path, params: { cartitem: data } + Cartitem.count.must_equal old_cartitem_count + 1 + + end + + it "should create a cartitem when a cart already exists " do + data = { + product_id: Product.first.id, + quantity: 3 + } + post cartitems_path, params: { cartitem: data } + old_cartitem_count = Cartitem.count + + data2 = { + product_id: Product.last.id, + quantity: 5 + } + post cartitems_path, params: {cartitem: data2} + Cartitem.count.must_equal old_cartitem_count + 1 + end + + it "should not create a cartitem when quantity is missing" do + old_cartitem_count = Cartitem.count + + data = { + product_id: Product.first.id, + } + + post cartitems_path, params: { cartitem: data } + + Cartitem.count.must_equal old_cartitem_count + end + + it "should not create a new cartitem when another cartitem has the same product" do + data = { + product_id: Product.first.id, + quantity: 3 + } + post cartitems_path, params: { cartitem: data } + + old_cartitem_count = Cartitem.count + + data2 = { + product_id: Product.first.id, + quantity: 5 + } + + post cartitems_path, params: {cartitem: data2} + Cartitem.count.must_equal old_cartitem_count + end + + it "should not create a new cartitem when there is no such product" do + data = { + product_id: Product.first.id, + quantity: 3 + } + + post cartitems_path, params: { cartitem: data } + + old_cartitem_count = Cartitem.count + + data2 = { + product_id: Product.last.id + 100, + quantity: 5 + } + + post cartitems_path, params: {cartitem: data2} + flash[:result_text].must_equal "It was not possible to add this product to the cart" + + end + + end + + describe "update" do + it "can update quantity if there is enough stock" do + cartitem = Cartitem.first + old_quantity = cartitem.quantity + new_cartitem_info = { quantity: (old_quantity - 1) } + patch cartitem_path(cartitem), params: { cartitem: new_cartitem_info } + + must_respond_with :redirect + must_redirect_to cart_path(cartitem.cart) + Cartitem.first.quantity.must_equal (old_quantity - 1) + end + + + it "does not update quantity if there is not enough stock" do + cartitem = Cartitem.first + old_quantity = cartitem.quantity + new_cartitem_info = { quantity: 100 } + + patch cartitem_path(cartitem), params: { cartitem: new_cartitem_info } + + must_respond_with :redirect + must_redirect_to cart_path(cartitem.cart) + Cartitem.first.quantity.must_equal old_quantity + end + + it "does not update the DB for bogus data" do + cartitem = Cartitem.first + old_quantity = cartitem.quantity + new_cartitem_info = { quantity: -1 } + + patch cartitem_path(cartitem), params: { cartitem: new_cartitem_info } + + must_respond_with :redirect + must_redirect_to cart_path(cartitem.cart) + Cartitem.first.quantity.must_equal old_quantity + end + end + + + describe "destroy" do + it "succeeds for an existing cartitem ID" do + item = Cartitem.first + item_id = item.id + old_item_count = Cartitem.count + + delete cartitem_path(item_id) + + Cartitem.count.must_equal old_item_count - 1 + must_respond_with :redirect + must_redirect_to cart_path(item.cart_id) + end + + it "renders not_found and does not update the DB for a bogus cartitem ID" do + item_id = Cartitem.last.id + 1 + old_item_count = Cartitem.count + + delete cartitem_path(item_id) + + Cartitem.count.must_equal old_item_count + must_respond_with :not_found + end + end +end diff --git a/test/controllers/carts_controller_test.rb b/test/controllers/carts_controller_test.rb new file mode 100644 index 0000000000..f7e02bb48e --- /dev/null +++ b/test/controllers/carts_controller_test.rb @@ -0,0 +1,68 @@ +require "test_helper" + +describe CartsController do + + describe "show" do + it "succeeds for an extant cart ID" do + cart_id = Cart.first.id + get cart_path(cart_id) + must_respond_with :success + end + + it "renders not_found for a bogus cart ID" do + cart_id = Cart.last.id + 1 + get cart_path(cart_id) + must_respond_with :not_found + end + end + + describe "empty_cart" do + it "succeeds for an extant cart ID" do + data = { + product_id: Product.first.id, + quantity: 3 + } + post cartitems_path, params: { cartitem: data } + + cart = Cart.find_by(id: session[:cart_id]) + + patch empty_cart_path(cart.id) + + cart.total_items.must_equal 0 + must_respond_with :redirect + must_redirect_to cart_path(cart.id) + end + + it "renders not_found and does not update the DB for a bogus cart ID" do + cart_id = Cart.last.id + 1 + old_item_count = Cartitem.count + + patch empty_cart_path(cart_id) + + Cartitem.count.must_equal old_item_count + must_respond_with :not_found + end + + it "redirects if there are no items in the cart" do + cart = Cart.first + cart.cartitems.destroy_all + + patch empty_cart_path(cart.id) + + must_respond_with :redirect + must_redirect_to cart_path(cart.id) + end + + it "does not allow to empty other user's cart" do + cart = Cart.first + items_in_cart = cart.cartitems.count + old_item_count = Cartitem.count + + cart = Cart.last + patch empty_cart_path(cart.id) + + must_respond_with :redirect + flash[:status].must_equal :failure + end + end +end diff --git a/test/controllers/categories_controller_test.rb b/test/controllers/categories_controller_test.rb new file mode 100644 index 0000000000..ab0b370234 --- /dev/null +++ b/test/controllers/categories_controller_test.rb @@ -0,0 +1,82 @@ +require "test_helper" + +describe CategoriesController do + describe "guest user" do + describe 'index' do + it 'can succeed with all categories' do + Category.count.must_be :>, 0 + + get categories_path + must_respond_with :success + end + + it 'can succeed with no categories' do + Category.destroy_all + + get categories_path + must_respond_with :success + end + end + + describe 'show' do + it "can find an existing category" do + get category_path(Category.first) + must_respond_with :success + end + + it "renders 404 not found for a fake id" do + fake_category_id = Category.last.id + 1 + get category_path(fake_category_id) + must_respond_with :not_found + end + end + + end + + describe "login merchant" do + before do + @merchant = Merchant.first + login(@merchant) + end + + describe "new" do + it "responds with success" do + get new_merchant_category_path(@merchant) + must_respond_with :success + end + end + + describe "create" do + it "can create a valid category" do + category_data = { + name: "fluffy" + } + + old_category_count = Category.count + + Category.new(category_data).must_be :valid? + + post merchant_categories_path(@merchant), params: { category: category_data} + + must_respond_with :redirect + Category.count.must_equal old_category_count + 1 + Category.last.name.must_equal "fluffy" + end + + it "can not create a category which is already existed" do + category_data = { + name: "cake" + } + + old_category_count = Category.count + + Category.new(category_data).must_be :invalid? + + post merchant_categories_path(@merchant), params: { category: category_data} + + must_respond_with :bad_request + Category.count.must_equal old_category_count + end + end + end +end diff --git a/test/controllers/merchants_controller_test.rb b/test/controllers/merchants_controller_test.rb new file mode 100644 index 0000000000..25ff4acec0 --- /dev/null +++ b/test/controllers/merchants_controller_test.rb @@ -0,0 +1,53 @@ +require "test_helper" + +describe MerchantsController do + + describe "index" do + it "succeeds when there are merchants" do + Merchant.count.must_be :>, 0 + + get merchants_path + must_respond_with :success + end + + it "succeeds when there are no merchants" do + Product.destroy_all + Merchant.destroy_all + get merchants_path + must_respond_with :success + end + end + + describe "show" do + before do + @merchant1 = Merchant.first + end + + it "Should redirect back if merchant is not logged in" do + get merchant_path(@merchant1) + must_respond_with :redirect + end + + it "renders 404 not_found for a bogus merchant ID" do + merchant404 = Merchant.last.id + 404 + get merchant_path(merchant404) + must_respond_with :not_found + end + + it "responds with failure if merchant is not logged in" do + skip + end + end + + describe "display" do + end + + describe "show_products" do + end + + describe "by_name" do + end + + + +end diff --git a/test/controllers/orders_controller_test.rb b/test/controllers/orders_controller_test.rb new file mode 100644 index 0000000000..95d1779e35 --- /dev/null +++ b/test/controllers/orders_controller_test.rb @@ -0,0 +1,266 @@ +require "test_helper" + +describe OrdersController do + describe "new" do + it "succeeds" do + cartitem_data = { + cart: carts(:cart_three), + product: Product.first, + quantity: 1 + } + post cartitems_path, params: { cartitem: cartitem_data } + + get new_order_path, params: { cart_id: carts(:cart_three).id } + must_respond_with :success + end + end + + describe "create" do + it "succeeds with the required inputs" do + cartitem_data = { + cart: carts(:cart_three), + product: Product.first, + quantity: 1 + } + post cartitems_path, params: { cartitem: cartitem_data } + + order_data = { + cart: carts(:cart_three), + status: "pending" + } + Order.new(order_data).must_be :valid? + old_order_count = Order.count + + post orders_path, params: { order: order_data } + + must_respond_with :redirect + must_redirect_to edit_order_path(Order.last.id) + Order.count.must_equal old_order_count + 1 + end + + it "returns bad request if the order status is not pending" do + cartitem_data = { + cart: carts(:cart_three), + product: Product.first, + quantity: 1 + } + post cartitems_path, params: { cartitem: cartitem_data } + + order_data = { + cart: carts(:cart_three), + status: "paid" + } + old_order_count = Order.count + + post orders_path, params: { order: order_data } + + must_respond_with :bad_request + Order.count.must_equal old_order_count + end + end + + describe "show" do + it "succeeds for an existing order id" do + order_id = Order.first.id + + get order_path(order_id) + must_respond_with :success + end + + it "responds with not_found for a bogus order ID" do + order_id = Order.last.id + 1 + + get order_path(order_id) + must_respond_with :not_found + end + end + + describe "edit" do + it "succeeds for an existing order ID" do + order_id = Order.first.id + + get edit_order_path(order_id) + must_respond_with :success + end + + it "responds with not_found for a bogus order ID" do + order_id = Order.last.id + 1 + + get edit_order_path(order_id) + must_respond_with :not_found + end + end + + describe "update" do + it "successfully updates the order, stock and visibility of product with valid data" do + order_id = orders(:order_three).id + product_id = products(:cheesecake).id + update_data = { + name: "Sam", + email: "sam@somethingcool.com", + creditcard: "1234123412341234", + expiration_month: "2020-04-16", + expiration_year: "2020-04-16", + name_on_card: "Sammy", + cvv: "123", + mail_address: "123 Main Street", + billing_address: "123 Main Street", + zipcode: "11111" + } + products(:cheesecake).stock.must_equal 6 + products(:cheesecake).visible.must_equal true + + patch order_path(order_id), params: { order: update_data } + + must_respond_with :redirect + must_redirect_to order_path(order_id) + Order.find_by(id: order_id).name.must_equal "Sam" + Product.find_by(id: product_id).stock.must_equal 0 + Product.find_by(id: product_id).visible.must_equal false + end + + it "redirects and does not update the order, stock, or visibility if quantity is greater than stock" do + product = Product.first + cartitem_data = { + cart: carts(:cart_four), + product: product, + quantity: 20 + } + result = Cartitem.new(cartitem_data).valid? + result.must_equal true + post cartitems_path, params: { cartitem: cartitem_data } + + order_data = { + cart: carts(:cart_four), + status: "pending" + } + result = Order.new(order_data).valid? + result.must_equal true + post orders_path, params: { order: order_data } + order = Order.last + update_data = { + name: "Sam", + email: "sam@somethingcool.com", + creditcard: "1234123412341234", + expiration_month: "2020-04-16", + expiration_year: "2020-04-16", + name_on_card: "Sammy", + cvv: "123", + mail_address: "123 Main Street", + billing_address: "123 Main Street", + zipcode: "11111", + status: "paid" + } + + patch order_path(order.id), params: { order: update_data } + end + + it "responds with bad request and does not update the order with bogus data" do + order_id = orders(:order_three).id + update_data = { + name: "Sam", + email: "sam@somethingcool.com", + creditcard: nil, + expiration_month: "2020-04-16", + expiration_year: "2020-04-16", + name_on_card: "Sammy", + cvv: "123", + mail_address: "123 Main Street", + billing_address: "123 Main Street", + zipcode: "11111", + status: "paid" + } + + patch order_path(order_id), params: { order: update_data } + + must_respond_with :bad_request + Order.find_by(id: order_id).name.must_be_nil + end + end + + describe "change_status" do + it "redirects and updates the database if the merchant is logged in" do + login(merchants(:wini)) + order = orders(:order_one) + order.status.must_equal "completed" + update_data = { + status: "paid" + } + + patch change_status_path(order.id), params: { order: update_data } + + must_respond_with :redirect + must_redirect_to order_path(order.id) + Order.find_by(id: order.id).status.must_equal "paid" + end + + it "responds with unauthorized and does not update DB if the order does not correspond to the logged in merchant" do + login(merchants(:grace)) + order = orders(:order_one) + order.status.must_equal "completed" + update_data = { + status: "paid" + } + + patch change_status_path(order.id), params: { order: update_data } + + must_respond_with :bad_request + Order.find_by(id: order.id).status.must_equal "completed" + end + + it "responds with unauthorized and does not update the database if the merchant is not logged in" do + order = orders(:order_one) + order.status.must_equal "completed" + update_data = { + status: "paid" + } + + patch change_status_path(order.id), params: { order: update_data } + + must_respond_with :bad_request + Order.find_by(id: order.id).status.must_equal "completed" + end + + it "responds with bad request and does not update the database if the order data is bogus" do + login(merchants(:wini)) + order = orders(:order_one) + order.status.must_equal "completed" + update_data = { + status: nil + } + + patch change_status_path(order.id), params: { order: update_data } + + must_respond_with :bad_request + Order.find_by(id: order.id).status.must_equal "completed" + end + + it "responds with not found if order id is bogus" do + order_id = Order.last.id + 1 + update_data = { + status: nil + } + + patch change_status_path(order_id), params: { order: update_data } + + must_respond_with :not_found + end + end + + describe "my_order" do + it "succeeds for an existing order ID when the merchant is logged in" do + login(merchants(:wini)) + order_id = orders(:order_one).id + + get my_order_path(order_id) + must_respond_with :success + end + + it "renders not_found for a bogus order ID" do + order_id = Order.last.id + 1 + + get my_order_path(order_id) + must_respond_with :not_found + end + end +end diff --git a/test/controllers/products_controller_test.rb b/test/controllers/products_controller_test.rb new file mode 100644 index 0000000000..0bfda888c7 --- /dev/null +++ b/test/controllers/products_controller_test.rb @@ -0,0 +1,261 @@ +require "test_helper" + +describe ProductsController do + #NOTE: index and show are available to both logged in merchants and guest users + + describe 'index' do + it 'can succeed with all products' do + Product.count.must_be :>, 0 + + get products_path + must_respond_with :success + end + + it 'can succeed with no products' do + Product.destroy_all + + get products_path + must_respond_with :success + end + end + + describe 'show' do + it "can find an exsisting product" do + get product_path(Product.first) + must_respond_with :success + end + + it "renders 404 not found for a fake id" do + fake_product_id = Product.last.id + 1 + get product_path(fake_product_id) + + must_respond_with :not_found + end + end + # + # The following methods are only available to a logged in user aka a 'merchant'. + + describe 'logged in merchant' do + before do + @merchant = Merchant.first + login(Merchant.first) + end + + describe 'new' do + it 'responds with success for a logged in user' do + get new_product_path + must_respond_with :success + end + end + + describe 'create' do + it 'can add a valid product' do + product_data = products(:cheesecake) + + counting = Product.count + + product_data.must_be :valid? + + post products_path, params: { product: product_data.attributes } + + must_respond_with :redirect + must_redirect_to product_path(Product.last.id) + + Product.count.must_equal counting + 1 + Product.last.name.must_equal product_data[:name] + end + + it 'will not add an invalid product' do + product_data = { + name: " " + } + old_count = Product.count + + # Assumptions + Product.new(product_data).wont_be :valid? + + # Act + post products_path, params: { product: product_data } + + # Assert + must_respond_with :bad_request + Product.count.must_equal old_count + end + end + + describe 'edit' do + it 'sends success if the product does exsist' do + get edit_product_path(Product.first) + must_respond_with :success + end + + it 'sends not_found if the book does not exsist' do + product_id = Product.last.id + 1 + + get edit_product_path(product_id) + + must_respond_with :not_found + end + end + + describe 'update' do + it 'updates an exsisting product with valid data' do + product = Product.first + product_data = product.attributes + product_data[:name] = "some updated name" + + # Assumptions + product.assign_attributes(product_data) + product.must_be :valid? + + # Act + patch product_path(product), params: { product: product_data } + + # Assert + must_redirect_to product_path(product) + + product.reload + product.name.must_equal product_data[:name] + end + + it 'sends a bad request for invalid data' do + product = Product.first + product_data = product.attributes + product_data[:name] = "" + + # Assumptions + product.assign_attributes(product_data) + product.wont_be :valid? + + # Act + patch product_path(product), params: { product: product_data } + + # Assert + must_respond_with :bad_request + + product.reload + product.name.wont_equal product_data[:name] + end + end + + describe 'retire' do + before do + login(Merchant.first) + @product = Product.first + @product.merchant_id = @merchant.id + end + + it 'can successfully retire a product that still has stock' do + @product.stock = 4 + + patch retire_path(@product) + @product.reload + @product.wont_be :visible + must_respond_with :redirect + flash[:status].must_equal :success + end + + it "retires an item that stock has reached 0" do + @product.stock = 0 + @product.visible = false + @product.save + + patch retire_path(@product) + @product.reload + @product.wont_be :visible + must_respond_with :redirect + flash[:status].must_equal :failure + end + + it 'will not retire an item that is already retired and will respond with result_text if the product cannot be retired' do + @product.visible = false + @product.stock = 0 + @product.save + patch retire_path(@product) + @product.reload + @product.wont_be :visible + must_respond_with :redirect + flash[:status].must_equal :failure + end + + it 'unretires an item thats stock is not 0' do + @product.visible = false + @product.stock = 5 + @product.save + patch retire_path(@product) + @product.reload + @product.must_be :visible + must_respond_with :redirect + flash[:status].must_equal :success + end + end + end + # These are tests for a guest user. No ability to alter or create products. Only show and index. + + + describe 'guest user' do + it 'rejects requests for new product form' do + get new_product_path + must_respond_with :redirect + end + + it 'rejects requests to create a product' do + product_data = products(:cheesecake) + + old_count = Product.count + + # Assumptions + product_data.must_be :valid? + + # Act + post products_path, params: { product: product_data } + + must_respond_with :redirect + Product.count.must_equal old_count + end + + it 'rejectes requests for the edit form' do + get edit_product_path(Product.first.id) + must_respond_with :redirect + end + + it 'rejects requests to update a product' do + product = Product.first + product_data = product.attributes + product_data[:name] = "some updated name" + + # Assumptions + product.assign_attributes(product_data) + product.must_be :valid? + + # Act + patch product_path(product), params: {product: product_data} + + must_respond_with :unauthorized + end + + it 'rejects requests to retire a product' do + product = Product.first + product_data = product.attributes + + product.assign_attributes(product_data) + product.must_be :valid? + + patch retire_path(product), params: {product: product_data} + + must_respond_with :redirect + flash[:status].must_equal :failure + end + end + + describe 'by_name' do + it 'can find a product by name' do + skip + end + + it 'will respond with Not Found, if the name does not exsist' do + skip + end + end + +end diff --git a/test/controllers/reviews_controller_test.rb b/test/controllers/reviews_controller_test.rb new file mode 100644 index 0000000000..b9324b2eaa --- /dev/null +++ b/test/controllers/reviews_controller_test.rb @@ -0,0 +1,78 @@ +require "test_helper" + +describe ReviewsController do + describe 'new' do + it 'responds with success' do + get new_review_path + must_respond_with :success + end + end + + describe "create" do + it "creates a review with valid data for an existing product" do + review_data = { + rating: 2, + comment: "excellent", + product_id: Product.first.id + } + Review.new(review_data).must_be :valid? + old_review_count = Review.count + + post reviews_path, params: { review: review_data } + + must_respond_with :redirect + must_redirect_to product_path(review_data[:product_id]) + Review.count.must_equal old_review_count + 1 + Review.last.comment.must_equal review_data[:comment] + end + end + + it "renders forbidden and does not update the DB when product belongs to the merchant" do + product = Product.first + merchant = product.merchant + login(merchant) + + review_data = { + rating: 2, + comment: "excellent", + product_id: product.id + } + Review.new(review_data).must_be :valid? + old_review_count = Review.count + + post reviews_path, params: { review: review_data } + + must_respond_with :forbidden + Review.count.must_equal old_review_count + end + + it "renders bad request and does not update the DB for bogus data" do + review_data = { + rating: 0, + comment: "disgusting", + product_id: Product.first.id + } + Review.new(review_data).wont_be :valid? + old_review_count = Review.count + + post reviews_path, params: { review: review_data } + + must_respond_with :bad_request + Review.count.must_equal old_review_count + end + + it "renders not found and does not update the DB for a fake product" do + review_data = { + rating: 3, + comment: "average", + product_id: Product.last.id + 1 + } + Review.new(review_data).wont_be :valid? + old_review_count = Review.count + + post reviews_path, params: { review: review_data } + + must_respond_with :not_found + Review.count.must_equal old_review_count + end +end diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb new file mode 100644 index 0000000000..539975f782 --- /dev/null +++ b/test/controllers/sessions_controller_test.rb @@ -0,0 +1,96 @@ +require "test_helper" + +describe SessionsController do + + describe "auth_callback" do + it "logs in an existing merchant and redirect to root route" do + + merchant = Merchant.first + old_merchant_count = Merchant.count + login(merchant) + + must_redirect_to root_path + Merchant.count.must_equal old_merchant_count + session[:merchant_id].must_equal merchant.id + end + + it "creates a DB entry for a new merchant and redirect to root path" do + merchant = Merchant.new( + provider: "github", + uid: 500, + email: "dada@test.org", + username: "dadadatest" + ) + + merchant.must_be :valid? + old_merchant_count = Merchant.count + + login(merchant) + + Merchant.count.must_equal old_merchant_count + 1 + must_redirect_to root_path + session[:merchant_id].must_equal Merchant.last.id + flash[:status].must_equal :success + flash[:result_text].must_equal "#{merchant.username} successfully logged in as a new merchant" + end + + it "does not log in with insufficient data and redirect to root path" do + merchant = Merchant.new( + provider: "github", + uid: 505, + email: "dada@test.org", + username: "" + ) + + merchant.wont_be :valid? + old_merchant_count = Merchant.count + + login(merchant) + + flash[:status].must_equal :failure + Merchant.count.must_equal old_merchant_count + must_redirect_to root_path + end + + it "does not log in if no uid" do + + merchant = Merchant.new( + provider: "github", + email: "dada@test.org", + username: "whatever" + ) + + old_merchant_count = Merchant.count + + login(merchant) + + flash[:error].must_equal "Could not log in" + Merchant.count.must_equal old_merchant_count + must_redirect_to root_path + end + + end + + describe "logout" do + it "logs out a login merchant" do + merchant = Merchant.first + login(merchant) + + delete logout_path + + session[:merchant_id].must_equal nil + end + + + it "can only log the login merchant account" do + merchant = Merchant.first + + delete logout_path + + must_redirect_to root_path + flash[:status].must_equal :failure + flash[:result_text].must_equal "You must login in" + end + end + +end diff --git a/test/fixtures/.keep b/test/fixtures/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/cartitems.yml b/test/fixtures/cartitems.yml new file mode 100644 index 0000000000..a61fb161b8 --- /dev/null +++ b/test/fixtures/cartitems.yml @@ -0,0 +1,26 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# + +item_one: + cart: cart_one + product: bonbon + quantity: 4 + +item_two: + cart: cart_one + product: cheesecake + quantity: 2 + +item_three: + cart: cart_two + product: macaron + quantity: 1 + +item_four: + cart: cart_three + product: cheesecake + quantity: 6 diff --git a/test/fixtures/carts.yml b/test/fixtures/carts.yml new file mode 100644 index 0000000000..d1176ab7d7 --- /dev/null +++ b/test/fixtures/carts.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +cart_one: {} + +cart_two: {} + +cart_three: {} + +cart_four: {} diff --git a/test/fixtures/categories.yml b/test/fixtures/categories.yml new file mode 100644 index 0000000000..26c5ef6d8c --- /dev/null +++ b/test/fixtures/categories.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +cake: + name: cake +# +bitesize: + name: bite size treat diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/merchants.yml b/test/fixtures/merchants.yml new file mode 100644 index 0000000000..a076505b54 --- /dev/null +++ b/test/fixtures/merchants.yml @@ -0,0 +1,41 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below + +ada: + provider: github + uid: 12345 + email: ada@adadevelopersacademy.org + username: countess_ada + +grace: + provider: github + uid: 13371337 + email: grace@hooper.net + username: graceful_hoops + +coco: + provider: github + uid: 124827 + email: coco@adadevelopersacademy.org + username: brown_coco + +jenny: + provider: github + uid: 447345 + email: jenny@adadevelopersacademy.org + username: great_jenny + +analisa: + provider: github + uid: 639135 + username: analisa + email: analist@alibonbon.com + +wini: + provider: github + uid: 96527 + username: wini + email: wini@alibonbon.com diff --git a/test/fixtures/orders.yml b/test/fixtures/orders.yml new file mode 100644 index 0000000000..87eba9bb17 --- /dev/null +++ b/test/fixtures/orders.yml @@ -0,0 +1,38 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# + +order_one: + cart: cart_one + name: Grace Hopper + email: grace@somethingcool.com + creditcard: 1234123412341234 + expiration_month: 2020-04-16 + expiration_year: 2020-04-16 + name_on_card: Grace Hopper + cvv: 123 + mail_address: 345 Main Street + billing_address: 345 Main Street + zipcode: 54321 + status: completed + +order_two: + cart: cart_two + name: Ada Lovelace + email: ada@somethingcool.com + creditcard: 1234123412341234 + expiration_month: 2020-04-16 + expiration_year: 2020-04-16 + name_on_card: Ada Lovelace + cvv: 123 + mail_address: 123 Main Street + billing_address: 123 Main Street + zipcode: 11111 + status: pending + +order_three: + cart: cart_three + status: pending diff --git a/test/fixtures/products.yml b/test/fixtures/products.yml new file mode 100644 index 0000000000..37f5482f46 --- /dev/null +++ b/test/fixtures/products.yml @@ -0,0 +1,35 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# + +cheesecake: + merchant: analisa + name: cheesecake + price: 499 + stock: 6 + visible: true + image: "https://images.pexels.com/photos/219293/pexels-photo-219293.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350" + visible: true + + +bonbon: + merchant: wini + name: bonbon + price: 329 + stock: 10 + visible: true + image: "https://images.pexels.com/photos/219293/pexels-photo-219293.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350" + visible: true + + +macaron: + merchant: wini + name: macaron + price: 400 + stock: 19 + visible: true + image: "https://images.pexels.com/photos/219293/pexels-photo-219293.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=350" + visible: true diff --git a/test/fixtures/reviews.yml b/test/fixtures/reviews.yml new file mode 100644 index 0000000000..b77b19b193 --- /dev/null +++ b/test/fixtures/reviews.yml @@ -0,0 +1,20 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +review_one: + product: cheesecake + rating: 1 + comment: too dry + +review_two: + product: cheesecake + rating: 5 + comment: this is the greatest cake ever + +review_three: + product: bonbon + rating: 3 + comment: excellent but overpriced diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/cart_test.rb b/test/models/cart_test.rb new file mode 100644 index 0000000000..d4eb3f56ca --- /dev/null +++ b/test/models/cart_test.rb @@ -0,0 +1,99 @@ +require "test_helper" + +describe Cart do + describe 'validations' do + it 'can be created without any fields' do + cart = Cart.new + + result = cart.valid? + + result.must_equal true + end + end + + describe 'relations' do + before do + @cart = Cart.create! + end + + it 'connects to cartitems and cartitem_ids' do + cartitem = Cartitem.create!(product: Product.first, cart: @cart, quantity: 2) + + @cart.cartitems.must_include cartitem + @cart.cartitem_ids.must_include cartitem.id + end + + it 'connects to products and product_ids through cartitems' do + product = Product.create!(name: 'key lime pie', price: 399, merchant: Merchant.first) + cartitem = Cartitem.create!(product: product, cart: @cart, quantity: 2) + + @cart.products.must_include product + @cart.product_ids.must_include product.id + end + + it 'connects to order and order_id' do + cart = carts(:cart_one) + cart.order.must_equal orders(:order_one) + end + end + + describe "subtotal method" do + it "calculates subtotal for all items in the cart" do + cart = carts(:cart_one) + result = cart.subtotal + result.must_equal 2314 + end + + it "returns 0 if there are no items in the cart" do + cart = Cart.first + cart.cartitems.destroy_all + cart.cartitems.count.must_equal 0 + cart.subtotal.must_equal 0 + end + end + + describe "total_items method" do + it "calculates the total items in the cart" do + cart = carts(:cart_one) + result = cart.total_items + result.must_equal 6 + end + + it "returns 0 if there are no items in the cart" do + cart = Cart.first + cart.cartitems.destroy_all + result = cart.total_items + result.must_equal 0 + end + end + + describe "tax method" do + it "calculates the tax for items in a cart" do + cart = carts(:cart_one) + result = cart.tax + result.must_equal 231.4 + end + + it "returns 0 if there are no items in the cart" do + cart = Cart.first + cart.cartitems.destroy_all + result = cart.tax + result.must_equal 0 + end + end + + describe "total_cost method" do + it "calculates the total cost for items in a cart" do + cart = carts(:cart_one) + result = cart.total_cost + result.must_equal 2545.4 + end + + it "returns 0 if there are no items in the cart" do + cart = Cart.first + cart.cartitems.destroy_all + result = cart.total_cost + result.must_equal 0 + end + end +end diff --git a/test/models/cartitem_test.rb b/test/models/cartitem_test.rb new file mode 100644 index 0000000000..260fb18c46 --- /dev/null +++ b/test/models/cartitem_test.rb @@ -0,0 +1,77 @@ +require "test_helper" + +describe Cartitem do + describe 'validations' do + before do + @cartitem = Cartitem.new(product: Product.first, cart: Cart.first, quantity: 1) + end + + it 'can be created with all required fields' do + result = @cartitem.valid? + + result.must_equal true + end + + it 'is invalid without a product' do + @cartitem.product = nil + + result = @cartitem.valid? + + result.must_equal false + end + + it 'is invalid without a cart' do + @cartitem.cart = nil + + result = @cartitem.valid? + + result.must_equal false + end + + it 'is invalid without a quantity' do + @cartitem.quantity = nil + + result = @cartitem.valid? + + result.must_equal false + end + + it 'is invalid if quantity is not an integer' do + @cartitem.quantity = 3.5 + + result = @cartitem.valid? + + result.must_equal false + end + + it 'is invalid if quantity is less than 1' do + @cartitem.quantity = 0 + + result = @cartitem.valid? + + result.must_equal false + end + end + + describe 'relations' do + it 'connects to products and product_ids' do + cartitem = cartitems(:item_three) + cartitem.product.must_equal products(:macaron) + cartitem.product_id.must_equal products(:macaron).id + end + + it 'connects to carts and cart_ids' do + cartitem = cartitems(:item_one) + cartitem.cart.must_equal carts(:cart_one) + cartitem.cart_id.must_equal carts(:cart_one).id + end + end + + describe "subtotal method" do + it "calculates the subtotal for a cartitem" do + cartitem = cartitems(:item_two) + result = cartitem.subtotal + result.must_equal 998 + end + end +end diff --git a/test/models/category_test.rb b/test/models/category_test.rb new file mode 100644 index 0000000000..ed50a95baf --- /dev/null +++ b/test/models/category_test.rb @@ -0,0 +1,117 @@ +require "test_helper" + +describe Category do + describe 'validations' do + before do + @category = Category.new(name: 'new category') + end + + it 'can be created with all required fields' do + result = @category.valid? + + result.must_equal true + end + + it 'is invalid without a name' do + @category.name = nil + + result = @category.valid? + + result.must_equal false + end + + it 'is invalid with a duplicate name' do + existing_category = Category.first + @category.name = existing_category.name + + result = @category.valid? + + result.must_equal false + end + end + + describe 'relations' do + it 'has many products' do + category = Category.first + product_one = Product.first + product_two = Product.last + + category.products << product_one + category.products << product_two + + category.products.must_include product_one + category.products.must_include product_two + category.product_ids.must_include product_one.id + category.product_ids.must_include product_two.id + end + end + + describe 'show_four' do + it 'shows first four products of the category' do + category = Category.first + product1 = { + name: 'product1', + price: 233, + merchant: Merchant.first + } + product2 = { + name: 'product2', + price: 233, + merchant: Merchant.first + } + product3 = { + name: 'product3', + price: 233, + merchant: Merchant.first + } + product4 = { + name: 'product4', + price: 233, + merchant: Merchant.first + } + product5 = { + name: 'product5', + price: 233, + merchant: Merchant.first + } + new_products = [product1, product2, product3, product4, product5] + new_products.each do |pro| + category.products << Product.create(pro) + end + + result = category.show_four + result.count.must_equal 4 + result.wont_include product5 + end + + it "returns all products if there is four or less than four products in the category" do + category = Category.first + product1 = { + name: 'product1', + price: 233, + merchant: Merchant.first + } + product2 = { + name: 'product2', + price: 233, + merchant: Merchant.first + } + product3 = { + name: 'product3', + price: 233, + merchant: Merchant.first + } + + new_products = [product1, product2, product3] + new_products.each do |pro| + category.products << Product.create(pro) + end + + result = category.show_four + + result.count.must_equal 3 + result[0].name.must_equal product1[:name] + result[0].price.must_equal product1[:price] + end + end +end diff --git a/test/models/merchant_test.rb b/test/models/merchant_test.rb new file mode 100644 index 0000000000..4c31e85a6c --- /dev/null +++ b/test/models/merchant_test.rb @@ -0,0 +1,343 @@ +require "test_helper" + +describe Merchant do + describe 'validations' do + before do + @merchant = Merchant.new(username: 'dan', email: 'dan@alibonbon.com') + end + + it 'can be created with all required fields' do + result = @merchant.valid? + + result.must_equal true + end + + it 'is invalid without a username' do + @merchant.username = nil + + result = @merchant.valid? + + result.must_equal false + end + + it 'is invalid without an email' do + @merchant.email = nil + + result = @merchant.valid? + + result.must_equal false + end + + it 'is invalid with a duplicate username' do + existing_merchant = Merchant.first + @merchant.username = existing_merchant.username + + result = @merchant.valid? + + result.must_equal false + end + + it 'is invalid with a duplicate email' do + existing_merchant = Merchant.first + @merchant.email = existing_merchant.email + + result = @merchant.valid? + + result.must_equal false + end + end + + + describe 'relations' do + it 'has many products' do + merchant = merchants(:wini) + product = Product.create!(name: 'key lime pie', price: 399, merchant: merchant) + + merchant.products.must_include product + merchant.product_ids.must_include product.id + end + end + + describe 'show_four' do + it 'shows first four products of the merchant' do + merchant = Merchant.first + product1 = { + name: 'product1', + price: 233, + merchant: merchant + } + product2 = { + name: 'product2', + price: 233, + merchant: merchant + } + product3 = { + name: 'product3', + price: 233, + merchant: merchant + } + product4 = { + name: 'product4', + price: 233, + merchant: merchant + } + product5 = { + name: 'product5', + price: 233, + merchant: merchant + } + new_products = [product1, product2, product3, product4, product5] + new_products.each do |pro| + Product.create(pro) + end + + result = merchant.show_four + result.count.must_equal 4 + result.wont_include product5 + end + + it "returns all products if there is four or less than four products in the merchant" do + merchant = Merchant.first + product1 = { + name: 'product1', + price: 233, + merchant: merchant + } + product2 = { + name: 'product2', + price: 233, + merchant: merchant + } + product3 = { + name: 'product3', + price: 233, + merchant: merchant + } + + new_products = [product1, product2, product3] + new_products.each do |pro| + merchant.products << Product.create(pro) + end + + result = merchant.show_four + + result.count.must_equal 3 + result[0].name.must_equal product1[:name] + result[0].price.must_equal product1[:price] + end + end + + describe "build_from_github" do + end + + describe "visible_products" do + before do + @merchant1 = Merchant.first + product1 = { + name: 'product1', + price: 233, + merchant: @merchant1, + visible: true + } + product2 = { + name: 'product2', + price: 233, + merchant: @merchant1, + visible: false + } + product3 = { + name: 'product3', + price: 233, + merchant: @merchant1, + visible: true + } + + new_products = [product1, product2, product3] + new_products.each do |prod| + @merchant1.products << Product.create(prod) + end + end + + it "should show the correct quantity of visible products" do + + @merchant1.visible_products.count.must_equal 2 + + end + + it "should return 0 when no visible products" do + @merchant1.products.each do |prod| + prod.visible = false + end + + @merchant1.visible_products.count.must_equal 0 + end + end + + describe "invisible_products" do + before do + @merchant1 = Merchant.first + product1 = { + name: 'product1', + price: 233, + merchant: @merchant1, + visible: true + } + product2 = { + name: 'product2', + price: 233, + merchant: @merchant1, + visible: false + } + product3 = { + name: 'product3', + price: 233, + merchant: @merchant1, + visible: true + } + + new_products = [product1, product2, product3] + new_products.each do |prod| + @merchant1.products << Product.create(prod) + end + end + + it "should show the correct quantity of invisible products" do + + @merchant1.invisible_products.count.must_equal 1 + + end + + it "should return 0 when no visible products" do + @merchant1.products.each do |prod| + prod.visible = true + end + + @merchant1.invisible_products.count.must_equal 0 + end + end + + describe "my_cartitems" do + before do + @merchant1 = Merchant.first + product1 = { + name: 'product1', + price: 233, + merchant: @merchant1, + } + product2 = { + name: 'product2', + price: 233, + merchant: @merchant1, + } + + new_products = [product1, product2] + new_products.each do |prod| + @merchant1.products << Product.create(prod) + end + end + + it "should return all the cartitems that exist that are my products" do + cartitem1 = Cartitem.create(product_id: @merchant1.products.first.id, cart_id: Cart.first.id, quantity: 2) + cartitem2 = Cartitem.create(product_id: @merchant1.products.last.id, cart_id: Cart.first.id, quantity: 1) + cartitem3 = Cartitem.create(product_id: @merchant1.products.first.id, cart_id: Cart.last.id, quantity: 3) + + @merchant1.my_cartitems.count.must_equal 3 + end + + it "should return 0 when no cartitems that have my products" do + cartitem1 = Cartitem.create(product_id: Product.last.id + 1, cart_id: Cart.first.id, quantity: 2) + cartitem2 = Cartitem.create(product_id: Product.last.id + 2, cart_id: Cart.first.id, quantity: 1) + cartitem3 = Cartitem.create(product_id: Product.last.id + 3, cart_id: Cart.last.id, quantity: 3) + + @merchant1.my_cartitems.count.must_equal 0 + end + end + + describe "my_orders" do + before do + @merchant1 = Merchant.first + product1 = { + name: 'product1', + price: 233, + merchant: @merchant1, + } + product2 = { + name: 'product2', + price: 233, + merchant: @merchant1, + } + + new_products = [product1, product2] + new_products.each do |prod| + @merchant1.products << Product.create(prod) + end + @cart1 = Cart.create + @cart2 = Cart.create + + cartitem1 = Cartitem.create(product_id: @merchant1.products.first.id, cart_id: @cart1.id, quantity: 2) + cartitem2 = Cartitem.create(product_id: @merchant1.products.last.id, cart_id: @cart2.id, quantity: 1) + cartitem3 = Cartitem.create(product_id: @merchant1.products.first.id, cart_id: @cart2.id, quantity: 3) + + end + + it "should return every order that has one of your products" do + order1 = Order.create(cart_id: @cart1.id, status: "pending") + + @merchant1.my_orders.count.must_equal 1 + end + end + + describe "my_total_revenue" do + it "should return 0 when there are no orders for merchant" do + merchant = Merchant.create!(uid: 12355, provider: "github", username: "hi", email: "hi@something.com") + + merchant.my_total_revenue.must_equal 0 + end + + it "should return the sum of all your products in different orders" do + merchant = merchants(:wini) + merchant.my_orders.count.must_equal 2 + merchant.my_total_revenue.must_equal 1716 + end + end + + describe "my_revenue_by_status" do + it "returns 0 when there are no orders for a merchant" do + merchant = merchants(:ada) + merchant.my_orders.count.must_equal 0 + merchant.my_revenue_by_status("pending").must_equal 0 + merchant.my_revenue_by_status("paid").must_equal 0 + merchant.my_revenue_by_status("completed").must_equal 0 + merchant.my_revenue_by_status("cancelled").must_equal 0 + end + + it "returns the appropriate revenue by status" do + merchant = merchants(:wini) + merchant.my_orders.count.must_equal 2 + merchant.my_revenue_by_status("pending").must_equal 400 + merchant.my_revenue_by_status("completed").must_equal 1316 + merchant.my_revenue_by_status("paid").must_equal 0 + merchant.my_revenue_by_status("cancelled").must_equal 0 + end + + it "responds to changes in order status" do + merchant = merchants(:wini) + order_1 = orders(:order_one) + order_2 = orders(:order_two) + + order_1.status.must_equal "completed" + order_2.status.must_equal "pending" + + pending_revenue_before = merchant.my_revenue_by_status("pending") + completed_revenue_before = merchant.my_revenue_by_status("completed") + + order_1.update_attributes(status: "cancelled") + order_2.update_attributes(status: "completed") + + merchant.my_revenue_by_status("cancelled").must_equal completed_revenue_before + merchant.my_revenue_by_status("completed").must_equal pending_revenue_before + merchant.my_revenue_by_status("pending").must_equal 0 + merchant.my_revenue_by_status("paid").must_equal 0 + end + end +end diff --git a/test/models/order_test.rb b/test/models/order_test.rb new file mode 100644 index 0000000000..a9e7517be9 --- /dev/null +++ b/test/models/order_test.rb @@ -0,0 +1,219 @@ +require "test_helper" + +describe Order do + describe 'validations' do + before do + @order = Order.new( + cart: carts(:cart_one), + name: 'Drew', + email: 'drew@somthingcool.com', + creditcard: '1234123412341234', + expiration_month: Date.parse('8/10/18'), + expiration_year: Date.parse('8/10/18'), + name_on_card: 'Drew Crisanti', + cvv: '123', + mail_address: '123 Main Street, Small Town, USA', + billing_address: '123 Main Street, Small Town, USA', + status: 'completed', + zipcode: '11111') + end + + it 'can be created with all required fields when status is pending' do + order = Order.new(cart: carts(:cart_two), status: 'pending') + + result = order.valid? + + result.must_equal true + end + + it 'can be created with all required fields when status is not pending' do + result = @order.valid? + + result.must_equal true + end + + it 'is invalid when name is missing and status is not pending' do + @order.name = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when email is missing and status is not pending' do + @order.email = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when credit card is missing and status is not pending' do + @order.creditcard = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when credit card is not 16 characters and status is not pending' do + @order.creditcard = '12341234' + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when expiration month is missing and status is not pending' do + @order.expiration_month = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when expiration year is missing and status is not pending' do + @order.expiration_year = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when name on card is missing and status is not pending' do + @order.name_on_card = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when cvv is missing and status is not pending' do + @order.cvv = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when cvv is less than three characters and status is not pending' do + @order.cvv = '12' + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when cvv is more than four characters and status is not pending' do + @order.cvv = '12345' + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when mail address is missing and status is not pending' do + @order.mail_address = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when billing address is missing and status is not pending' do + @order.billing_address = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when status is missing and status is not pending' do + @order.status = nil + + result = @order.valid? + + result.must_equal false + end + + it 'is invalid when zip code is missing and status is not pending' do + @order.zipcode = nil + + result = @order.valid? + + result.must_equal false + end + end + + describe 'relations' do + it 'connects to cart and cart_id' do + order = orders(:order_one) + order.cart.must_equal carts(:cart_one) + order.cart_id.must_equal carts(:cart_one).id + end + + it 'can set a cart' do + order = Order.new( + name: 'Drew', + email: 'drew@somthingcool.com', + creditcard: '1234123412341234', + expiration_month: Date.parse('8/10/18'), + expiration_year: Date.parse('8/10/18'), + name_on_card: 'Drew Crisanti', + cvv: '123', + mail_address: '123 Main Street, Small Town, USA', + billing_address: '123 Main Street, Small Town, USA', + status: 'pending', + zipcode: '11111') + order.cart = carts(:cart_two) + + order.cart_id.must_equal carts(:cart_two).id + end + end + + describe "is_pending method" do + it "returns false if status is not pending" do + order = orders(:order_one) + result = order.is_pending + result.must_equal false + end + + it "returns false if status is not pending" do + order = orders(:order_two) + result = order.is_pending + result.must_equal true + end + end + + describe "order_tax method" do + it "calculates the tax for items in an order" do + order = orders(:order_one) + result = order.order_tax + result.must_equal 231.4 + end + + it "returns 0 if there are no items in the order" do + order = Order.first + order_items = Cartitem.where(cart_id: order.cart_id) + order_items.destroy_all + result = order.order_tax + result.must_equal 0 + end + end + + describe "total_cost method" do + it "calculates the total cost for items in an order" do + order = orders(:order_one) + result = order.total + result.must_equal 2545.4 + end + + it "returns 0 if there are no items in the order" do + order = Order.first + order_items = Cartitem.where(cart_id: order.cart_id) + order_items.destroy_all + result = order.total + result.must_equal 0 + end + end +end diff --git a/test/models/product_test.rb b/test/models/product_test.rb new file mode 100644 index 0000000000..cc515c7aa2 --- /dev/null +++ b/test/models/product_test.rb @@ -0,0 +1,161 @@ +require "test_helper" + +describe Product do + describe 'validations' do + before do + @merchant = Merchant.first + @product = Product.new( + name: 'new product', + price: 500, + merchant: @merchant + ) + end + + it 'can be created with all required fields' do + result = @product.valid? + + result.must_equal true + end + + it 'is invalid without a name' do + @product.name = nil + + result = @product.valid? + + result.must_equal false + end + + it 'is invalid without a price' do + @product.price = nil + + result = @product.valid? + + result.must_equal false + end + + it 'is invalid without a merchant' do + @product.merchant = nil + + result = @product.valid? + + result.must_equal false + end + + it 'is invalid if price is not an integer' do + @product.price = 3.99 + + result = @product.valid? + + result.must_equal false + end + + it 'is invalid if price is less than 0' do + @product.price = -99 + + result = @product.valid? + + result.must_equal false + end + end + + describe 'relations' do + before do + @product = Product.create!(name: 'something good', price: 99, merchant: Merchant.first) + end + + it 'connects to cartitem and cartitem_id' do + cartitem = Cartitem.create!(product: @product, cart: Cart.first, quantity: 2) + + @product.cartitems.must_include cartitem + @product.cartitem_ids.must_include cartitem.id + end + + it 'prevents destroying product if there exsists a cartitem' do + product = products(:cheesecake) + + product.destroy.must_equal false + + product.persisted?.must_equal true + end + + + it 'connects to review and review_id' do + review = Review.create!(product: @product, rating: 5) + + @product.reviews.must_include review + @product.review_ids.must_include review.id + end + + it 'connects to categories and category ids' do + category = Category.first + + @product.categories << category + @product.category_ids.must_include category.id + end + + it 'connects to merchant and merchant_id' do + product = products(:bonbon) + + product.merchant.must_equal merchants(:wini) + product.merchant_id.must_equal merchants(:wini).id + end + + it 'can set the merchant' do + product = Product.new(name: 'tasty treat', price: 199) + product.merchant = merchants(:analisa) + + product.merchant_id.must_equal merchants(:analisa).id + end + end + + describe 'average_rating method' do + it 'returns the average of all the ratings on a product' do + product = products(:cheesecake) + + product.average_rating.must_equal 3 + end + + it 'returns nil when there are no reviews' do + product = products(:macaron) + + product.average_rating.must_be_nil + end + + it 'returns the value when there is only one review of the one review' do + product = products(:bonbon) + + product.average_rating.must_equal 3 + end + end + + describe 'available? method' do + it 'returns true if the requested quantity is less than the inventory' do + product = products(:cheesecake) + product.stock.must_equal 6 + + result = product.available?(5) + + result.must_equal true + end + + it 'returns true if the requested quantity is equal to the inventory' do + product = products(:bonbon) + product.stock.must_equal 10 + + result = product.available?(10) + + result.must_equal true + end + + it 'returns false if the requested quantity is greater than the inventory' do + product = products(:macaron) + product.stock.must_equal 19 + + result = product.available?(20) + + result.must_equal false + end + end + + #TODO: Tests for def new_stock +end diff --git a/test/models/review_test.rb b/test/models/review_test.rb new file mode 100644 index 0000000000..9631777b12 --- /dev/null +++ b/test/models/review_test.rb @@ -0,0 +1,69 @@ +require "test_helper" + +describe Review do + describe 'validations' do + before do + @review = Review.new(rating: 4, product: products(:macaron)) + end + + it 'can be created with all required fields' do + result = @review.valid? + + result.must_equal true + end + + it 'is invalid without a rating' do + @review.rating = nil + + result = @review.valid? + + result.must_equal false + end + + it 'is invalid without a product' do + @review.product = nil + + result = @review.valid? + + result.must_equal false + end + + it 'is invalid when the rating is not an integer' do + @review.rating = 3.5 + + result = @review.valid? + + result.must_equal false + end + + it 'is invalid when the rating is less than 1' do + @review.rating = 0 + + result = @review.valid? + + result.must_equal false + end + + it 'is invalid when the rating is greater than 5' do + @review.rating = 6 + + result = @review.valid? + + result.must_equal false + end + end + + describe 'relations' do + it 'belongs to a product' do + review = reviews(:review_one) + review.product.must_equal products(:cheesecake) + review.product_id.must_equal products(:cheesecake).id + end + + it 'can set the product' do + review = Review.new(rating: 4) + review.product = products(:macaron) + review.product_id.must_equal products(:macaron).id + end + end +end diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..c119cf91c4 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,52 @@ +if ENV["RAILS_ENV"] = "test" + require 'simplecov' + SimpleCov.start 'rails' + puts "required simplecov" +end + +require File.expand_path("../../config/environment", __FILE__) +require "rails/test_help" +require "minitest/rails" +require "minitest/reporters" # for Colorized output + +# For colorful output! +Minitest::Reporters.use!( + Minitest::Reporters::SpecReporter.new, + ENV, + Minitest.backtrace_filter +) + + +# To add Capybara feature tests add `gem "minitest-rails-capybara"` +# to the test group in the Gemfile and uncomment the following: +# require "minitest/rails/capybara" + +# Uncomment for awesome colorful output +# require "minitest/pride" + +class ActiveSupport::TestCase + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + # Add more helper methods to be used by all tests here... + + def setup + OmniAuth.config.test_mode = true + end + + def mock_auth_hash(merchant) + return { + provider: merchant.provider, + uid: merchant.uid, + info: { + email: merchant.email, + name: merchant.username + } + } + end + + def login(merchant) + OmniAuth.config.mock_auth[:github] = OmniAuth::AuthHash.new(mock_auth_hash(merchant)) + get auth_callback_path(:github) + end + +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000000..e69de29bb2