diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..0deead5ef5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+# 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
+
+# Ignore uploaded files in development
+/storage/*
+!/storage/.keep
+
+/node_modules
+/yarn-error.log
+
+/public/assets
+.byebug_history
+
+# Ignore master key for decrypting credentials and more.
+/config/master.key
+.env
+
+# Ignore simplecov and test data in simplecov report
+/coverage.data
+/coverage
+.DS_Store
diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000000..160fe391c8
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+2.5.5
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..9e63e70089
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,17 @@
+{
+ "workbench.colorCustomizations": {
+ "activityBar.background": "#760c5f",
+ "activityBar.foreground": "#e7e7e7",
+ "activityBar.inactiveForeground": "#e7e7e799",
+ "activityBarBadge.background": "#74910f",
+ "activityBarBadge.foreground": "#e7e7e7",
+ "titleBar.activeBackground": "#48073a",
+ "titleBar.inactiveBackground": "#48073a99",
+ "titleBar.activeForeground": "#e7e7e7",
+ "titleBar.inactiveForeground": "#e7e7e799",
+ "statusBar.background": "#48073a",
+ "statusBarItem.hoverBackground": "#760c5f",
+ "statusBar.foreground": "#e7e7e7"
+ },
+ "peacock.color": "#48073a"
+}
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000000..f2a7dca89d
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,96 @@
+source 'https://rubygems.org'
+git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
+ruby '2.5.5'
+
+# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
+gem 'rails', '~> 5.2.3'
+# Use postgresql as the database for Active Record
+gem 'pg', '>= 0.18', '< 2.0'
+# Use Puma as the app server
+gem 'puma', '~> 3.11'
+# 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 'mini_racer', platforms: :ruby
+
+# Use CoffeeScript for .coffee assets and views
+# gem 'coffee-rails', '~> 4.2'
+# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
+gem 'turbolinks', '~> 5'
+# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
+gem 'jbuilder', '~> 2.5'
+# Use Redis adapter to run Action Cable in production
+# gem 'redis', '~> 4.0'
+# Use ActiveModel has_secure_password
+# gem 'bcrypt', '~> 3.1.7'
+
+# Use ActiveStorage variant
+# gem 'mini_magick', '~> 4.8'
+
+# Use Capistrano for deployment
+# gem 'capistrano-rails', group: :development
+
+# Reduces boot times through caching; required in config/boot.rb
+gem 'bootsnap', '>= 1.1.0', require: false
+
+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]
+ gem 'pry-rails'
+ gem 'pry-byebug'
+end
+
+group :development do
+ # Access an interactive console on exception pages or by calling '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
+
+group :test do
+ # Adds support for Capybara system testing and selenium driver
+ gem 'capybara', '>= 2.15'
+ gem 'selenium-webdriver'
+ # Easy installation and use of chromedriver to run system tests with Chrome
+ gem 'chromedriver-helper'
+end
+
+# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+
+gem 'jquery-rails'
+gem 'jquery-turbolinks'
+gem 'bootstrap', '~> 4.1.3'
+group :development, :test do
+ gem 'pry-rails'
+end
+
+group :development do
+ gem 'debase', '>= 0.2.4.1'
+ gem 'ruby-debug-ide', '>= 0.7.0'
+end
+
+group :development do
+ gem 'better_errors'
+ gem 'binding_of_caller'
+ gem 'guard'
+ gem 'guard-minitest'
+end
+
+group :test do
+ gem 'minitest-rails'
+ gem 'minitest-reporters'
+ gem 'simplecov', require: false
+end
+
+gem "omniauth"
+gem "omniauth-github"
+
+
+gem 'bootstrap-multiselect-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000..10c62e56f6
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,326 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ actioncable (5.2.3)
+ actionpack (= 5.2.3)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ actionmailer (5.2.3)
+ actionpack (= 5.2.3)
+ actionview (= 5.2.3)
+ activejob (= 5.2.3)
+ mail (~> 2.5, >= 2.5.4)
+ rails-dom-testing (~> 2.0)
+ actionpack (5.2.3)
+ actionview (= 5.2.3)
+ activesupport (= 5.2.3)
+ rack (~> 2.0)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ actionview (5.2.3)
+ activesupport (= 5.2.3)
+ builder (~> 3.1)
+ erubi (~> 1.4)
+ rails-dom-testing (~> 2.0)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
+ activejob (5.2.3)
+ activesupport (= 5.2.3)
+ globalid (>= 0.3.6)
+ activemodel (5.2.3)
+ activesupport (= 5.2.3)
+ activerecord (5.2.3)
+ activemodel (= 5.2.3)
+ activesupport (= 5.2.3)
+ arel (>= 9.0)
+ activestorage (5.2.3)
+ actionpack (= 5.2.3)
+ activerecord (= 5.2.3)
+ marcel (~> 0.3.1)
+ activesupport (5.2.3)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ ansi (1.5.0)
+ archive-zip (0.12.0)
+ io-like (~> 0.3.0)
+ arel (9.0.0)
+ autoprefixer-rails (9.6.5)
+ execjs
+ better_errors (2.5.1)
+ coderay (>= 1.0.0)
+ erubi (>= 1.0.0)
+ rack (>= 0.9.0)
+ bindex (0.8.1)
+ binding_of_caller (0.8.0)
+ debug_inspector (>= 0.0.1)
+ bootsnap (1.4.5)
+ msgpack (~> 1.0)
+ bootstrap (4.1.3)
+ autoprefixer-rails (>= 6.0.3)
+ popper_js (>= 1.12.9, < 2)
+ sass (>= 3.5.2)
+ bootstrap-multiselect-rails (0.9.9)
+ rails (>= 4.0.0)
+ builder (3.2.3)
+ byebug (11.0.1)
+ capybara (3.29.0)
+ addressable
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.8)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (~> 1.5)
+ xpath (~> 3.2)
+ childprocess (3.0.0)
+ chromedriver-helper (2.1.1)
+ archive-zip (~> 0.10)
+ nokogiri (~> 1.8)
+ coderay (1.1.2)
+ concurrent-ruby (1.1.5)
+ crass (1.0.5)
+ debase (0.2.4.1)
+ debase-ruby_core_source (>= 0.10.2)
+ debase-ruby_core_source (0.10.6)
+ debug_inspector (0.0.3)
+ docile (1.3.2)
+ dotenv (2.7.5)
+ dotenv-rails (2.7.5)
+ dotenv (= 2.7.5)
+ railties (>= 3.2, < 6.1)
+ erubi (1.9.0)
+ execjs (2.7.0)
+ faraday (0.17.0)
+ multipart-post (>= 1.2, < 3)
+ ffi (1.11.1)
+ formatador (0.2.5)
+ globalid (0.4.2)
+ activesupport (>= 4.2.0)
+ guard (2.15.1)
+ formatador (>= 0.2.4)
+ listen (>= 2.7, < 4.0)
+ lumberjack (>= 1.0.12, < 2.0)
+ nenv (~> 0.1)
+ notiffany (~> 0.0)
+ pry (>= 0.9.12)
+ shellany (~> 0.0)
+ thor (>= 0.18.1)
+ guard-compat (1.2.1)
+ guard-minitest (2.4.6)
+ guard-compat (~> 1.2)
+ minitest (>= 3.0)
+ hashie (3.6.0)
+ i18n (1.7.0)
+ concurrent-ruby (~> 1.0)
+ io-like (0.3.0)
+ jbuilder (2.9.1)
+ activesupport (>= 4.2.0)
+ jquery-rails (4.3.5)
+ 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 (2.2.1)
+ listen (3.1.5)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ ruby_dep (~> 1.2)
+ loofah (2.3.1)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.5.9)
+ lumberjack (1.0.13)
+ mail (2.7.1)
+ mini_mime (>= 0.1.1)
+ marcel (0.3.3)
+ mimemagic (~> 0.3.2)
+ method_source (0.9.2)
+ mimemagic (0.3.3)
+ mini_mime (1.0.2)
+ mini_portile2 (2.4.0)
+ minitest (5.12.2)
+ minitest-rails (5.2.0)
+ minitest (~> 5.10)
+ railties (~> 5.2.0)
+ minitest-reporters (1.4.1)
+ ansi
+ builder
+ minitest (>= 5.0)
+ ruby-progressbar
+ msgpack (1.3.1)
+ multi_json (1.14.1)
+ multi_xml (0.6.0)
+ multipart-post (2.1.1)
+ nenv (0.3.0)
+ nio4r (2.5.2)
+ nokogiri (1.10.4)
+ mini_portile2 (~> 2.4.0)
+ notiffany (0.1.3)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
+ oauth2 (1.4.2)
+ faraday (>= 0.8, < 2.0)
+ jwt (>= 1.0, < 3.0)
+ multi_json (~> 1.3)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 3)
+ omniauth (1.9.0)
+ hashie (>= 3.4.6, < 3.7.0)
+ rack (>= 1.6.2, < 3)
+ omniauth-github (1.3.0)
+ omniauth (~> 1.5)
+ omniauth-oauth2 (>= 1.4.0, < 2.0)
+ omniauth-oauth2 (1.6.0)
+ oauth2 (~> 1.1)
+ omniauth (~> 1.9)
+ pg (1.1.4)
+ popper_js (1.14.5)
+ pry (0.12.2)
+ coderay (~> 1.1.0)
+ method_source (~> 0.9.0)
+ pry-byebug (3.7.0)
+ byebug (~> 11.0)
+ pry (~> 0.10)
+ pry-rails (0.3.9)
+ pry (>= 0.10.4)
+ public_suffix (4.0.1)
+ puma (3.12.1)
+ rack (2.0.7)
+ rack-test (1.1.0)
+ rack (>= 1.0, < 3)
+ rails (5.2.3)
+ actioncable (= 5.2.3)
+ actionmailer (= 5.2.3)
+ actionpack (= 5.2.3)
+ actionview (= 5.2.3)
+ activejob (= 5.2.3)
+ activemodel (= 5.2.3)
+ activerecord (= 5.2.3)
+ activestorage (= 5.2.3)
+ activesupport (= 5.2.3)
+ bundler (>= 1.3.0)
+ railties (= 5.2.3)
+ sprockets-rails (>= 2.0.0)
+ rails-dom-testing (2.0.3)
+ activesupport (>= 4.2.0)
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.3.0)
+ loofah (~> 2.3)
+ railties (5.2.3)
+ actionpack (= 5.2.3)
+ activesupport (= 5.2.3)
+ method_source
+ rake (>= 0.8.7)
+ thor (>= 0.19.0, < 2.0)
+ rake (13.0.0)
+ rb-fsevent (0.10.3)
+ rb-inotify (0.10.0)
+ ffi (~> 1.0)
+ regexp_parser (1.6.0)
+ ruby-debug-ide (0.7.0)
+ rake (>= 0.8.1)
+ ruby-progressbar (1.10.1)
+ ruby_dep (1.5.0)
+ rubyzip (2.0.0)
+ sass (3.7.4)
+ sass-listen (~> 4.0.0)
+ sass-listen (4.0.0)
+ rb-fsevent (~> 0.9, >= 0.9.4)
+ rb-inotify (~> 0.9, >= 0.9.7)
+ sass-rails (5.1.0)
+ railties (>= 5.2.0)
+ sass (~> 3.1)
+ sprockets (>= 2.8, < 4.0)
+ sprockets-rails (>= 2.0, < 4.0)
+ tilt (>= 1.1, < 3)
+ selenium-webdriver (3.142.6)
+ childprocess (>= 0.5, < 4.0)
+ rubyzip (>= 1.2.2)
+ shellany (0.0.1)
+ simplecov (0.17.1)
+ docile (~> 1.1)
+ json (>= 1.8, < 3)
+ simplecov-html (~> 0.10.0)
+ simplecov-html (0.10.2)
+ spring (2.1.0)
+ spring-watcher-listen (2.0.1)
+ listen (>= 2.7, < 4.0)
+ spring (>= 1.2, < 3.0)
+ sprockets (3.7.2)
+ concurrent-ruby (~> 1.0)
+ rack (> 1, < 3)
+ sprockets-rails (3.2.1)
+ actionpack (>= 4.0)
+ activesupport (>= 4.0)
+ sprockets (>= 3.0.0)
+ thor (0.20.3)
+ thread_safe (0.3.6)
+ tilt (2.0.10)
+ turbolinks (5.2.1)
+ turbolinks-source (~> 5.2)
+ turbolinks-source (5.2.0)
+ tzinfo (1.2.5)
+ thread_safe (~> 0.1)
+ uglifier (4.2.0)
+ execjs (>= 0.3.0, < 3)
+ web-console (3.7.0)
+ actionview (>= 5.0)
+ activemodel (>= 5.0)
+ bindex (>= 0.4.0)
+ railties (>= 5.0)
+ websocket-driver (0.7.1)
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.4)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ better_errors
+ binding_of_caller
+ bootsnap (>= 1.1.0)
+ bootstrap (~> 4.1.3)
+ bootstrap-multiselect-rails
+ byebug
+ capybara (>= 2.15)
+ chromedriver-helper
+ debase (>= 0.2.4.1)
+ dotenv-rails
+ guard
+ guard-minitest
+ jbuilder (~> 2.5)
+ jquery-rails
+ jquery-turbolinks
+ listen (>= 3.0.5, < 3.2)
+ minitest-rails
+ minitest-reporters
+ omniauth
+ omniauth-github
+ pg (>= 0.18, < 2.0)
+ pry-byebug
+ pry-rails
+ puma (~> 3.11)
+ rails (~> 5.2.3)
+ ruby-debug-ide (>= 0.7.0)
+ sass-rails (~> 5.0)
+ selenium-webdriver
+ simplecov
+ spring
+ spring-watcher-listen (~> 2.0.0)
+ turbolinks (~> 5)
+ tzinfo-data
+ uglifier (>= 1.3.0)
+ web-console (>= 3.3.0)
+
+RUBY VERSION
+ ruby 2.5.5p157
+
+BUNDLED WITH
+ 2.0.2
diff --git a/Guardfile b/Guardfile
new file mode 100644
index 0000000000..e34f706f4a
--- /dev/null
+++ b/Guardfile
@@ -0,0 +1,9 @@
+guard :minitest, autorun: false, spring: true do
+ watch(%r{^app/(.+).rb$}) { |m| "test/#{m[1]}_test.rb" }
+ watch(%r{^app/controllers/application_controller.rb$}) { 'test/controllers' }
+ watch(%r{^app/controllers/(.+)_controller.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
+ watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
+ watch(%r{^lib/(.+).rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
+ watch(%r{^test/.+_test.rb$})
+ watch(%r{^test/test_helper.rb$}) { 'test' }
+end
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/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 0000000000..36ee311c2d
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,23 @@
+// 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 jquery3
+ //= require popper
+ //= require bootstrap-sprockets
+//
+//= require rails-ujs
+//= require activestorage
+//= require turbolinks
+//= require_tree .
+
+
+//= require bootstrap-multiselect
\ No newline at end of file
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/channels/.keep b/app/assets/javascripts/channels/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/assets/javascripts/homepages.js b/app/assets/javascripts/homepages.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/homepages.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/order_items.js b/app/assets/javascripts/order_items.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/order_items.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/orders.js b/app/assets/javascripts/orders.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/orders.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/products.js b/app/assets/javascripts/products.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/products.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/javascripts/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/users.js b/app/assets/javascripts/users.js
new file mode 100644
index 0000000000..dee720facd
--- /dev/null
+++ b/app/assets/javascripts/users.js
@@ -0,0 +1,2 @@
+// Place all the behaviors and hooks related to the matching controller here.
+// All this logic will automatically be available in application.js.
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
new file mode 100644
index 0000000000..4bc35ee9ae
--- /dev/null
+++ b/app/assets/stylesheets/application.scss
@@ -0,0 +1,562 @@
+/* Custom bootstrap variables must be set or imported *before* bootstrap. */
+@import "bootstrap";
+/* Import scss content */
+@import "**/*";
+
+.btn-pretty {
+ color: white;
+ background-color: #9D6AB9;
+}
+
+.btn-pretty:hover {
+ color: white;
+ background-color: rgb(139, 57, 182);
+}
+
+.btn-green {
+ background-color: rgb(94, 170, 95);
+ color: white;
+}
+
+.btn-green:hover {
+ background-color: rgb(72, 135, 73);
+ color: white;
+}
+
+nav {
+ font-family: 'Lato', sans-serif;
+}
+
+.bg-merchant-nav {
+ background-color: rgb(94, 170, 95);
+}
+
+.navbar-toggler-icon{
+ background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0,0,0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E");
+}
+
+.top-brand-name {
+ font-family: 'Modak', cursive;
+ text-shadow: 0px 2px 3px #555;
+}
+
+.top-brand-name a:hover {
+ color: rgb(254, 219, 105);
+}
+
+p {
+ font-family: 'Lato', sans-serif;
+}
+
+// navbar header on every page
+.top-brand-name {
+ text-align: center;
+ font-size: 6rem;
+}
+.top-brand-name a, #dropdown-text {
+ color: white
+}
+
+.all-nav-parts-container {
+ background-color: rgb(255, 172, 172);
+}
+
+// login/logout and baskets
+.login-loggout-container {
+ margin-top: 1%;
+ margin-right: 1.5%;
+}
+
+.login-basket-nav {
+ display: flex;
+ justify-content: flex-end;
+ padding-top: 1%;
+ margin-right: 2%;
+
+}
+
+.logout-btn {
+ margin-top: 2%
+}
+
+.basket-img {
+ max-width: 5vw;
+}
+
+.cart-link {
+ margin-right: 1%;
+ background-color: transparent;
+}
+
+
+// main and footer on every page
+main {
+ min-height: 100vh;
+ background-color: rgb(254, 219, 105);
+ font-family: 'Lato', sans-serif;
+}
+
+footer {
+ text-align: center;
+ padding: 3%;
+ background-color: rgb(255, 172, 172);
+ color: white;
+}
+
+.page-title {
+ padding: 1%;
+ background-color: rgb(94, 170, 95);
+ color: white;
+ margin-bottom: 1%;
+ border-radius: 15px;
+}
+
+.page-container {
+ padding: 2%;
+}
+
+// cards styling for products
+.cards-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ align-content: center;
+}
+.card {
+ display:flex;
+ justify-content:space-evenly;
+ padding: 1em;
+ margin-top: 3%;
+ margin: 1%;
+ position: relative;
+ text-align: center;
+ border: solid 10px #9D6AB9;
+ box-shadow: 4px 8px rgba(136, 136, 136, 0.5);
+}
+
+.quick-add-btn {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+}
+
+.card-img-top {
+ max-height: 300px;
+ max-width: 300px;
+}
+
+// Fruit Links Header
+.fruit-img {
+ max-width: 7vw;
+}
+
+.nav-fruit-img {
+ max-width: 2em;
+ margin-right: 1%
+}
+
+.fruit-shape-links {
+ display: flex;
+ justify-content: space-evenly;
+ background-color: rgba(255, 255, 255, 0.8);
+}
+
+.fruit-img:hover, .basket-img:hover {
+ /* Start the shake animation and make the animation last for 0.5 seconds */
+ animation: shake 0.5s;
+
+ /* When the animation is finished, start again */
+ animation-iteration-count: infinite;
+}
+
+@keyframes shake {
+ 0% { transform: translate(1px, 1px) rotate(0deg); }
+ 10% { transform: translate(-1px, -2px) rotate(-1deg); }
+ 20% { transform: translate(-3px, 0px) rotate(1deg); }
+ 30% { transform: translate(3px, 2px) rotate(0deg); }
+ 40% { transform: translate(1px, -1px) rotate(1deg); }
+ 50% { transform: translate(-1px, 2px) rotate(-1deg); }
+ 60% { transform: translate(-3px, 1px) rotate(0deg); }
+ 70% { transform: translate(3px, 1px) rotate(-1deg); }
+ 80% { transform: translate(-1px, -1px) rotate(1deg); }
+ 90% { transform: translate(1px, 2px) rotate(0deg); }
+ 100% { transform: translate(1px, -2px) rotate(-1deg); }
+}
+
+// Homepage
+.category-card-container {
+ display: flex;
+}
+
+// General
+ul {
+ list-style: none;
+}
+
+form {
+ margin: 1em;
+}
+
+a {
+ color: rgb(75, 140, 76);
+}
+
+a:hover {
+ text-decoration: none;
+ color:#9D6AB9;
+}
+
+
+.product-thumbnail-cart {
+ max-width: 3em;
+ border: 3px solid #9D6AB9;
+}
+
+.product-thumbnail {
+ max-width: 3em;
+}
+
+.fruitstand-img {
+ max-width: 1.6em;
+}
+
+.new-form-page {
+ min-height: 300vh;
+}
+
+.homepage-container {
+ padding: 3%;
+ padding-bottom: 10%
+}
+
+.homepage-container h2 {
+ margin-top: 2.5%;
+ margin-bottom: 2.5%;
+ padding: 1%;
+ font-weight: bold;
+ font-size: 2.5rem;
+ border-bottom: 2px solid #5EAA5F;
+}
+
+
+.new-product-form {
+ display: flex;
+ justify-content: flex-start;
+ padding: 3%;
+ max-width: 40%;
+ background-color: white;
+ border: 4px solid #9D6AB9;
+ box-shadow: 4px 8px rgba(136, 136, 136, 0.5);
+ color: #9D6AB9;
+ font-weight: bold;
+}
+
+.review-product-form {
+ border: 4px solid #9D6AB9;
+ box-shadow: 4px 8px rgba(136, 136, 136, 0.5);
+ color: #9D6AB9;
+ font-weight: bold;
+ background-color: white;
+ padding: 2%;
+ max-width: 60%;
+}
+
+
+.text-fields {
+ margin-right: 5%;
+}
+
+.table {
+ background-color: white;
+ border: 4px solid #9D6AB9;
+}
+
+thead {
+ background-color: rgb(255, 172, 172);
+ text-align: center;
+ border: 4px solid #9D6AB9;
+}
+
+th, th a{
+ color:white;
+ font-weight: bold;
+}
+
+// show product page
+
+.show-product-img {
+ max-height: 300px;
+ border: solid 10px #9D6AB9;
+ box-shadow: 4px 8px rgba(136, 136, 136, 0.5);
+ margin-right: 5%;
+ float: left;
+}
+
+.product-show-head, .product-show-merchant {
+ margin-left: 5%
+}
+
+.product-show-head {
+ margin-top: 2%;
+ margin-bottom: 2%;
+ font-weight: bolder;
+ color: rgb(113, 72, 134);
+}
+
+.product-show-merchant {
+ margin-bottom: 2%;
+ font-weight: bold;
+}
+
+.add-to-cart-form {
+ margin: 0.5% 0;
+}
+
+.rating-form .btn {
+ margin-top: 2%
+}
+
+
+.product-show {
+ margin-left: 5%
+}
+
+.product-show h5 {
+ font-weight: bold;
+ margin: 1% 0
+}
+
+.rating-form {
+ display: flex;
+ flex-direction: column;
+ margin-top: 10%;
+ border-top: grey 1px solid;
+ padding: 5%
+}
+
+.rating-form h2 {
+ font-weight: bolder;
+ color: rgb(113, 72, 134);
+}
+
+.rating-form form {
+ margin: 2% 0
+}
+
+.rating-form div.form-row {
+ padding: 1% 0;
+
+}
+.product-details {
+ display: flex;
+ flex-direction: column;
+}
+
+.rating-img {
+ max-width: 2rem;
+}
+
+.review-containter {
+ line-height: 0.5rem;
+ margin: 2%;
+}
+
+.all-reviews-container {
+ border-bottom: grey 1px solid;
+ border-top: grey 1px solid;
+ padding: 5% 0;
+}
+
+.all-reviews-container h2 {
+ font-weight: bolder;
+ color: rgb(113, 72, 134);
+ margin-left: 5%;
+}
+
+.review-containter {
+ margin: 5% 5% 2% 5%
+}
+
+.review-containter h4, .review-containter p{
+ margin: 1% 0
+}
+
+.form-row {
+ padding: 1%;
+ font-weight: bold;
+}
+
+// Edit User Page
+.edit-header {
+ padding: 1em;
+ font-weight:bold;
+ text-align:center;
+}
+.edit-user {
+ margin-left:25%;
+ margin-right:25%;
+}
+
+
+.cart-table{
+ table-layout: fixed;
+ width: 75%;
+ margin-left: 10rem;
+ font-size: 1em;
+}
+
+
+ #checkout-section {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ margin-right: 14rem;
+ }
+
+#checkout-button {
+ max-width: 8rem;
+}
+
+.cart-quantity {
+ max-width: 4rem;
+}
+
+.confirmation-img {
+ max-width: 10%;
+ border: solid 2px #9D6AB9;
+ box-shadow: 1px 3px rgba(136, 136, 136, 0.5);
+}
+
+.confirmation-img-container {
+ display: flex;
+ justify-content: space-evenly;
+ margin: 5%;
+}
+
+.confirmation-shipping-container {
+ max-width: 30%;
+ margin-right: 1%;
+}
+
+
+.confirmation-welcome, .checkout-welcome {
+ text-align: center;
+ margin-top: 3%;
+}
+
+.confirmation-details-container {
+ display: flex;
+ justify-content: center;
+}
+
+.customer-service {
+ text-align: center;
+ margin-bottom: 2%;
+}
+
+.phone-icon {
+ max-width: 5%;
+ margin-bottom: 1%;
+}
+
+// Checkout Page
+
+.checkout-header{
+ margin-bottom:3%;
+}
+
+.checkout-card {
+ margin-left:20%;
+ margin-right:20%;
+}
+
+.checkout-billing-container {
+ margin-top: 5%;
+}
+
+.checkout-container h2 {
+ margin-bottom: 2%
+}
+
+.submit-btn {
+ margin-top: 2%;
+ margin-left: 15%;
+ margin-right:20%;
+}
+
+.order-items-display {
+ margin-top: 30px;
+}
+
+.cart-table > thead {
+ text-align: left;
+ font-size: 1.25em;
+}
+
+.cart-img {
+ width: 5%;
+}
+
+.cart-name {
+ width: 25%;
+}
+
+.cart-price {
+ width: 10%;
+}
+
+.cart-quantity {
+ width: 5%;
+}
+
+.cart-update {
+ width: 5%;
+}
+
+.cart-remove {
+ width: 10%;
+}
+
+#large-cart {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ max-height: 50vh;
+ margin-bottom: 20px;
+}
+
+.product-description {
+ color: rgb(94, 170, 95);
+}
+// current user page
+.current-user tr {
+ text-align: center;
+}
+
+.current-user h2 {
+ margin: 2% 0;
+}
+
+.current-user h3 {
+ margin: 4% 0 2% 0
+}
+
+.current-user .username {
+ color: rgb(113, 72, 134);
+}
+
+.order-show-page {
+ text-align: center;
+ padding: 2%
+}
+
+.order-show-page h1 {
+ color: rgb(113, 72, 134);
+ margin: 2%
+}
+
+.order-show-page section {
+ width: auto;
+ margin: 3% 30%;
+ padding: 2%;
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/homepages.scss b/app/assets/stylesheets/homepages.scss
new file mode 100644
index 0000000000..2305c36d10
--- /dev/null
+++ b/app/assets/stylesheets/homepages.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Homepages controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/order_items.scss b/app/assets/stylesheets/order_items.scss
new file mode 100644
index 0000000000..584862de9b
--- /dev/null
+++ b/app/assets/stylesheets/order_items.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the OrderItems controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/orders.scss b/app/assets/stylesheets/orders.scss
new file mode 100644
index 0000000000..741506954d
--- /dev/null
+++ b/app/assets/stylesheets/orders.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Orders controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/products.scss b/app/assets/stylesheets/products.scss
new file mode 100644
index 0000000000..bff386e55a
--- /dev/null
+++ b/app/assets/stylesheets/products.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Products controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss
new file mode 100644
index 0000000000..11bbb12cd5
--- /dev/null
+++ b/app/assets/stylesheets/reviews.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Reviews controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss
new file mode 100644
index 0000000000..31a2eacb84
--- /dev/null
+++ b/app/assets/stylesheets/users.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Users controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb
new file mode 100644
index 0000000000..d672697283
--- /dev/null
+++ b/app/channels/application_cable/channel.rb
@@ -0,0 +1,4 @@
+module ApplicationCable
+ class Channel < ActionCable::Channel::Base
+ end
+end
diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb
new file mode 100644
index 0000000000..0ff5442f47
--- /dev/null
+++ b/app/channels/application_cable/connection.rb
@@ -0,0 +1,4 @@
+module ApplicationCable
+ class Connection < ActionCable::Connection::Base
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
new file mode 100644
index 0000000000..181ccd3a1d
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,19 @@
+class ApplicationController < ActionController::Base
+ before_action :require_login
+ before_action :find_order
+
+ def current_user
+ @current_user ||= User.find(session[:user_id]) if session[:user_id]
+ end
+
+ def require_login
+ if current_user.nil?
+ flash[:error] = "You must be logged in to do that."
+ redirect_to root_path
+ end
+ end
+
+ def find_order
+ @current_order = Order.find_by(id: session[:cart_id])
+ 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/homepages_controller.rb b/app/controllers/homepages_controller.rb
new file mode 100644
index 0000000000..614ba2df21
--- /dev/null
+++ b/app/controllers/homepages_controller.rb
@@ -0,0 +1,7 @@
+class HomepagesController < ApplicationController
+ skip_before_action :require_login
+ skip_before_action :find_order
+
+ def index
+ end
+end
diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb
new file mode 100644
index 0000000000..f4d8bb18f4
--- /dev/null
+++ b/app/controllers/order_items_controller.rb
@@ -0,0 +1,80 @@
+class OrderItemsController < ApplicationController
+ skip_before_action :require_login
+ before_action :find_order
+
+ def create
+ if @current_order.nil?
+ @current_order = Order.new_order
+ session[:cart_id] = @current_order.id
+ end
+
+ @product = Product.find_by(id: params[:product_id])
+ if @product.nil?
+ flash[:error] = "Product no longer exists."
+ return head :not_found
+ end
+
+ input_quantity = order_item_params[:quantity].to_i
+
+ if !@product.quantity_available?(input_quantity)
+ flash[:error] = "Quantity entered (#{input_quantity}) is greater than available stock for #{@product.name}."
+ return redirect_back(fallback_location: cart_path)
+ elsif !@current_order.order_items.where(product: @product).empty?
+ order_item = @current_order.order_items.where(product: @product).first
+ order_item.increase_quantity(input_quantity)
+ flash[:success] = "#{@product.name} successfully added to your basket! (quantity: #{input_quantity})"
+ return redirect_back(fallback_location: cart_path)
+ else
+ order_item = OrderItem.new(
+ product: @product,
+ order: @current_order,
+ quantity: input_quantity
+ )
+ end
+
+ if order_item.save
+ flash[:success] = "#{@product.name} successfully added to your basket! (quantity: #{input_quantity})"
+ return redirect_back(fallback_location: cart_path)
+ else
+ flash[:error] = "#{@product.name} was not added to your basket."
+ flash[:errors] = order_item.errors.messages
+ return redirect_back(fallback_location: :root)
+ end
+ end
+
+ def update
+ order_item = OrderItem.find_by(id: params[:id])
+ product = order_item.product
+ input_quantity = order_item_params[:quantity].to_i
+
+ if product.quantity_available?(input_quantity)
+ if order_item.update(order_item_params)
+ flash[:success] = "#{product.name} successfully updated!"
+ else
+ flash[:error] = "Could not update quantity for #{product.name}."
+ flash[:errors] = order_item.errors.messages
+ end
+ else
+ flash[:error] = "Quantity entered (#{input_quantity}) is greater than available stock for #{product.name}."
+ end
+ return redirect_to cart_path
+ end
+
+ def destroy
+ order_item = OrderItem.find_by(id: params[:id])
+ if order_item
+ if order_item.destroy
+ flash[:success] = "#{order_item.product.name} successfully removed from your basket!"
+ else
+ flash.now[:error] = "A problem occurred. #{order_item.product.name} was not successfully removed from your basket."
+ end
+ end
+ return redirect_to cart_path
+ end
+
+ private
+
+ def order_item_params
+ return params.require(:order_item).permit(:product, :order, :quantity)
+ end
+end
diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb
new file mode 100644
index 0000000000..4de26e2326
--- /dev/null
+++ b/app/controllers/orders_controller.rb
@@ -0,0 +1,111 @@
+class OrdersController < ApplicationController
+ skip_before_action :require_login, only: [:cart, :checkout, :update_paid, :confirmation]
+ skip_before_action :find_order, only: [:show, :update_paid, :cancel_order, :complete_order]
+ before_action :find_order_params, only: [:show, :update_paid, :cancel_order, :complete_order]
+
+ def show
+ if !@order
+ head :not_found
+ return
+ elsif !@order.contain_orderitems?(@current_user)
+ flash[:error] = "You cannot check this order details!"
+ return redirect_to root_path
+ end
+ end
+
+ def cart
+ end
+
+ def checkout
+ if !@current_order
+ head :not_found
+ return
+ elsif @current_order.order_items.empty?
+ flash[:error] = "No item in the cart! Please add some items then checkout!"
+ return redirect_to root_path
+ end
+ end
+
+ def update_paid
+ if !@order
+ head :not_found
+ return
+ else
+ @order.status = "paid"
+ if @order.update(order_params)
+ @order.order_items.each do |item|
+ item.product.stock = item.product.update_quantity(item.quantity, @order.status)
+ item.product.save
+ end
+ flash[:success] = "Order #{@order.id} purchased successfully!"
+ return redirect_to confirmation_path
+
+ else
+ flash[:error] = "Something went wrong! Order was not placed and your card was not billed."
+ flash[:errors] = @order.errors.messages
+ return redirect_to cart_path
+ end
+ end
+ end
+
+ def confirmation
+ if @current_order && @current_order.status == 'paid'
+ session[:cart_id] = nil
+ else
+ head :not_found
+ return
+ end
+ end
+
+ def cancel_order
+ if !@order
+ head :not_found
+ return
+ else
+ if @order.contain_orderitems?(@current_user)
+ if @order.update(status: "cancelled")
+ @order.order_items.each do |item|
+ item.product.stock = item.product.update_quantity(item.quantity, @order.status)
+ item.product.save
+ end
+ flash[:success] = "Order #{@order.id} has been cancelled successfully!"
+ else
+ flash[:error] = "Something went wrong, order is not cancelled!"
+ end
+ else
+ flash[:error] = "You're not allowed to cancel this order!"
+ end
+ return redirect_to current_user_path
+ end
+ end
+
+ def complete_order
+ if !@order
+ head :not_found
+ return
+ else
+ if @order.contain_orderitems?(@current_user)
+ if @order.update(status: "completed")
+ flash[:success] = "Order #{@order.id} has been completed successfully!"
+ else
+ flash[:error] = "Something went wrong, order is not completed!"
+ end
+ else
+ flash[:error] = "You're not allowed to complete this order!"
+ end
+ return redirect_to current_user_path
+ end
+ end
+
+ private
+
+ def order_params
+ return params.require(:order).permit(:name, :email, :address, :cc_name, :cc_last4, :cc_exp, :cc_cvv, :billing_zip, status: "paid")
+ end
+
+ def find_order_params
+ @order = Order.find_by(id: params[:id])
+ end
+
+end
+
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
new file mode 100644
index 0000000000..e53f2c2499
--- /dev/null
+++ b/app/controllers/products_controller.rb
@@ -0,0 +1,99 @@
+class ProductsController < ApplicationController
+ before_action :find_product, only: [:show, :edit, :update]
+ skip_before_action :require_login, only: [:index, :show]
+ skip_before_action :find_order
+
+ def index
+ category_id = params[:category_id]
+
+ if category_id.nil?
+ if params[:search].nil?
+ @products = Product.active
+ else
+ @products = Product.search(params[:search].first)
+ @search_result = params[:search].first
+ params[:search] = nil
+ end
+ elsif category_id
+ @category = Category.find_by(id: category_id)
+ if @category
+ @products = @category.products.active
+ else
+ head :not_found
+ end
+ end
+ end
+
+ def show
+ if @product.nil?
+ head :not_found
+ return
+ end
+ end
+
+ def new
+ @product = Product.new
+ end
+
+ def create
+ @product = Product.new(product_params)
+
+ if params[:multiselect]
+ params[:multiselect].each do |id|
+ new_category = Category.where(id: id)
+ if !new_category.empty?
+ @product.categories << new_category
+ end
+ end
+ end
+ @product.user_id = session[:user_id]
+
+ if @product.save
+ if @current_user.merchant_name.nil?
+ flash[:success] = "Product #{@product.name} has been added successfully"
+ flash[:message] = "You merchant name is currently empty. Please add a merchant name to list your fruit stand with Fruitsy Merchants."
+ return redirect_to edit_user_path
+ else
+ flash[:success] = "Product #{@product.name} has been added successfully"
+ redirect_to product_path(@product.id)
+ return
+ end
+ elsif !@product.errors.empty?
+ flash.now[:error] = "New product was not added. Fix required fields before adding!"
+ render :new
+ return
+ else
+ flash.now[:error] = "Something went wrong! Product was not added."
+ render :new
+ return
+ end
+ end
+
+ def edit
+ if @product.nil?
+ redirect_to root_path
+ return
+ end
+ end
+
+ def update
+ if @product.update(product_params)
+ flash[:success] = "Product #{@product.name} has been updated successfully"
+ else
+ flash[:error] = "Something went wrong! Product can not be edited."
+ flash[:errors] = @product.errors.messages
+ end
+ redirect_to current_user_path
+ return
+ end
+
+ private
+
+ def find_product
+ @product = Product.find_by(id: params[:id])
+ end
+
+ def product_params
+ return params.require(:product).permit(:name, :price, :stock, :img_url, :description, :active, category_ids: [])
+ end
+end
diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb
new file mode 100644
index 0000000000..ba4b97346d
--- /dev/null
+++ b/app/controllers/reviews_controller.rb
@@ -0,0 +1,48 @@
+class ReviewsController < ApplicationController
+ skip_before_action :find_order
+ skip_before_action :require_login, only: [:create]
+
+ def create
+ @review = Review.new(review_params)
+
+ if @review.valid?
+ if current_user && @review.product.user_id == current_user.id
+ flash[:error] = "You can't review your own product!"
+ elsif current_user && !current_user.reviews.where(product_id: @review.product_id).empty?
+ flash[:error] = "You can't review a product more than once!"
+ elsif @review.save
+ flash[:success] = "Your #{Review.rating_sentiment(@review.rating)} review on #{@review.product.name} was added successfully!"
+ else
+ flash[:error] = "Something went wrong! Your review was not saved!"
+ end
+ else
+ flash[:error] = "Review was not added. Please check required fields before submitting."
+ flash[:errors] = @review.errors.messages
+ end
+
+ return redirect_to product_path(@review.product.id)
+ end
+
+ def destroy
+ review = Review.find_by(id: params[:id])
+ if review
+ product = review.product
+ if review.user_id && review.user_id == session[:user_id]
+ review.destroy
+ flash[:success] = "Your review was deleted!"
+ else
+ flash[:error] = "You cannot delete a review that isn't yours"
+ end
+ return redirect_to product_path(product.id)
+ else
+ flash[:error] = "The review doesn't exist anymore!"
+ return redirect_to root_path
+ end
+ end
+
+ private
+
+ def review_params
+ return params.require(:review).permit(:rating, :title, :description, :user_id, :product_id)
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000000..d23c823337
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,78 @@
+class UsersController < ApplicationController
+ skip_before_action :require_login, only: [:create, :show]
+ skip_before_action :find_order
+
+ def show
+ @user = User.find_by(id: params[:id])
+ if @user.nil?
+ head :not_found
+ return
+ end
+ @products = @user.products.active
+ end
+
+ def current
+ if params[:status].nil?
+ @order_items = []
+ @current_user.find_order_items.each do |order_item|
+ @order_items << order_item if order_item.order.status != 'pending'
+ end
+ else
+ @order_items = @current_user.filter_order_items(params[:status])
+ end
+ if params[:activestatus].nil?
+ @products = @current_user.products
+ else
+ @products = @current_user.products.where(active: params[:activestatus])
+ end
+ end
+
+ def create
+ auth_hash = request.env["omniauth.auth"]
+ user = User.find_by(uid: auth_hash[:uid], provider: params[:provider])
+ if user
+ if user.merchant_name
+ flash[:success] = "Welcome back #{user.merchant_name}! Manage your fruitstand or browse Fruitsy! "
+ else
+ flash[:success] = "Welcome back #{user.username}! Enjoy browsing Fruitsy."
+ end
+ else
+ user = User.build_from_github(auth_hash)
+ if user.save
+ flash[:success] = "Welcome to Fruitsy, #{user.username}!"
+ else
+ flash[:error] = "Oops, something happened! Could not create user account, please try again."
+ return redirect_to root_path
+ end
+ end
+ session[:user_id] = user.id
+ redirect_to root_path
+ end
+
+ def edit
+ end
+
+ def update
+ if @current_user.update(user_params)
+ flash[:success] = "#{@current_user.username} updated successfully!"
+ return redirect_to current_user_path
+ else
+ flash.now[:error] = "Please provide all required fields to edit your account."
+ return render :edit
+ end
+ end
+
+ def destroy
+ username = @current_user.username
+ session[:user_id] = nil
+ flash[:success] = "You are successfully logged out, #{username}!"
+ redirect_to root_path
+ end
+
+ private
+
+ def user_params
+ return params.require(:user).permit(:uid, :merchant_name, :email, :provider, :username)
+ end
+
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 0000000000..b8b3b3ac61
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,61 @@
+module ApplicationHelper
+
+ def readable_date(date)
+ return "[unknown]" unless date.instance_of?(ActiveSupport::TimeWithZone)
+ return (
+ "".html_safe +
+ date.strftime("%b %d, %Y")+
+ "".html_safe
+ )
+ end
+
+ def currency_format(num)
+ return nil unless num.instance_of?(Integer) || num.instance_of?(Float)
+ return("$" + sprintf('%.2f', num))
+ end
+
+ def fruit_image(code, fruit)
+ category = Category.find_by(name: fruit)
+ if category
+ image = image_tag "https://live.staticflickr.com/65535/#{code}_o.png", alt:"#{fruit} vector image", class:"fruit-img"
+ return link_to image, category_products_path(category.id)
+ end
+ end
+
+ def cart_empty_img_link
+ image = image_tag "https://live.staticflickr.com/65535/48971625503_83d9d1c039_o.png pcc", alt:"cart fruit basket empty image", class:"basket-img"
+ return link_to image, cart_path, data: { turbolinks: false }
+ end
+
+ def cart_full_img_link
+ image = image_tag "https://live.staticflickr.com/65535/48971625483_e04b973cc8_o.png", alt:"cart fruit basket full image", class:"basket-img"
+ return link_to image, cart_path, data: { turbolinks: false }
+ end
+
+ def product_img_link(product: product, img_url: img_url, product_class: product_class)
+ image = image_tag (product.img_url), class: product_class, alt:"#{product.name} product image"
+ return link_to image, product_path(product.id)
+ end
+
+ def rating_img
+ rating_img = "https://live.staticflickr.com/65535/48983817713_d25a3fba98_o.png"
+ return image_tag (rating_img), alt:"pineapple rating image", class: "rating-img"
+ end
+
+ def fruitstand_img
+ stand_img = "https://live.staticflickr.com/65535/48982995833_9783f655fb_o.png"
+ return image_tag (stand_img), alt:"fruitstand icon image", class: "fruitstand-img"
+ end
+
+ def nav_fruit_img
+ image = "https://live.staticflickr.com/65535/48989157882_0b4f1fae44_o.png"
+ return image_tag (image), alt:"fruit icon image", class: "nav-fruit-img"
+ end
+
+ def phone_icon
+ image = "https://live.staticflickr.com/65535/48994322702_80ca570ef1_o.png"
+ return image_tag (image), alt:"phone icon image", class: "phone-icon"
+ end
+end
diff --git a/app/helpers/homepages_helper.rb b/app/helpers/homepages_helper.rb
new file mode 100644
index 0000000000..4bd8098f37
--- /dev/null
+++ b/app/helpers/homepages_helper.rb
@@ -0,0 +1,2 @@
+module HomepagesHelper
+end
diff --git a/app/helpers/order_items_helper.rb b/app/helpers/order_items_helper.rb
new file mode 100644
index 0000000000..e197528ae1
--- /dev/null
+++ b/app/helpers/order_items_helper.rb
@@ -0,0 +1,2 @@
+module OrderItemsHelper
+end
diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb
new file mode 100644
index 0000000000..443227fd48
--- /dev/null
+++ b/app/helpers/orders_helper.rb
@@ -0,0 +1,2 @@
+module OrdersHelper
+end
diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb
new file mode 100644
index 0000000000..ab5c42b325
--- /dev/null
+++ b/app/helpers/products_helper.rb
@@ -0,0 +1,2 @@
+module ProductsHelper
+end
diff --git a/app/helpers/reviews_helper.rb b/app/helpers/reviews_helper.rb
new file mode 100644
index 0000000000..682b7b1abc
--- /dev/null
+++ b/app/helpers/reviews_helper.rb
@@ -0,0 +1,2 @@
+module ReviewsHelper
+end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
new file mode 100644
index 0000000000..2310a240d7
--- /dev/null
+++ b/app/helpers/users_helper.rb
@@ -0,0 +1,2 @@
+module UsersHelper
+end
diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb
new file mode 100644
index 0000000000..a009ace51c
--- /dev/null
+++ b/app/jobs/application_job.rb
@@ -0,0 +1,2 @@
+class ApplicationJob < ActiveJob::Base
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
new file mode 100644
index 0000000000..286b2239d1
--- /dev/null
+++ b/app/mailers/application_mailer.rb
@@ -0,0 +1,4 @@
+class ApplicationMailer < ActionMailer::Base
+ default from: 'from@example.com'
+ layout 'mailer'
+end
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
new file mode 100644
index 0000000000..10a4cba84d
--- /dev/null
+++ b/app/models/application_record.rb
@@ -0,0 +1,3 @@
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/app/models/category.rb b/app/models/category.rb
new file mode 100644
index 0000000000..25b72a9a62
--- /dev/null
+++ b/app/models/category.rb
@@ -0,0 +1,13 @@
+class Category < ApplicationRecord
+ validates :name, presence: true
+ has_and_belongs_to_many :products
+
+ def self.products_by_category(category_name)
+ category = Category.find_by(name: category_name)
+ if category
+ return category.products
+ else
+ return nil
+ end
+ end
+end
diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/models/order.rb b/app/models/order.rb
new file mode 100644
index 0000000000..33e6c3591a
--- /dev/null
+++ b/app/models/order.rb
@@ -0,0 +1,37 @@
+class Order < ApplicationRecord
+ has_many :order_items
+
+ validates :status, presence: true
+ validates :name, presence: true, if: :not_pending?
+ validates :email, presence: true, format: { with: /@/, message: "Email format must be valid." } , if: :not_pending?
+ validates :address, presence: true, if: :not_pending?
+ validates :cc_name, presence: true, if: :not_pending?
+ validates_numericality_of :cc_last4, greater_than_or_equal_to: 1000, less_than_or_equal_to: 9999, if: :not_pending?
+ validates :cc_exp, presence: true, if: :not_pending?
+ validates_numericality_of :cc_cvv, greater_than_or_equal_to: 100, less_than_or_equal_to: 9999, if: :not_pending?
+ validates :billing_zip, presence: true, if: :not_pending?
+
+ def not_pending?
+ status != 'pending'
+ end
+
+ def contain_orderitems?(user)
+ self.order_items.each do |order_item|
+ return true if order_item.product.user_id == user.id
+ end
+ return false
+ end
+
+ def total
+ total = 0
+ self.order_items.each do |orderitem|
+ total += orderitem.total
+ end
+ return total
+ end
+
+ def self.new_order
+ order = Order.create(status: "pending")
+ return order
+ end
+end
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
new file mode 100644
index 0000000000..5c34218b3d
--- /dev/null
+++ b/app/models/order_item.rb
@@ -0,0 +1,17 @@
+class OrderItem < ApplicationRecord
+ belongs_to :product
+ belongs_to :order
+
+ validates_numericality_of :quantity, greater_than: 0
+
+ def total
+ (self.quantity * self.product.price)
+ end
+
+ def increase_quantity(quantity)
+ existing_quantity = self.quantity
+ new_quantity = existing_quantity + quantity
+
+ return self.update(quantity: new_quantity)
+ end
+end
diff --git a/app/models/product.rb b/app/models/product.rb
new file mode 100644
index 0000000000..0ab7ac8d73
--- /dev/null
+++ b/app/models/product.rb
@@ -0,0 +1,62 @@
+class Product < ApplicationRecord
+ validates :name, presence: true, uniqueness: {scope: :user_id}
+ validates_length_of :name, minimum: 1, maximum: 50
+ validates :price, numericality: {greater_than: 0}
+ validates :stock, numericality: { only_integer: true, greater_than_or_equal_to: 0}
+ validates :user_id, presence: true
+ validates :img_url, presence: true
+ validates :description, presence: true
+
+ belongs_to :user
+ has_many :order_items, dependent: :nullify
+ has_many :reviews, dependent: :nullify
+ has_and_belongs_to_many :categories
+
+
+ def self.random_products(num)
+ return Product.all.shuffle.first(num)
+ end
+
+ def self.deals_under(price)
+ return Product.where("price < ?", price).shuffle
+ end
+
+ def quantity_available?(quantity)
+ if quantity > self.stock
+ return false
+ else
+ return true
+ end
+ end
+
+ def update_quantity(quantity, status)
+ if status == "paid"
+ self.stock -= quantity
+ elsif status == "cancelled"
+ self.stock += quantity
+ end
+ return self.stock
+ end
+
+ def avg_rating
+ reviews = self.reviews
+ if reviews.empty?
+ return nil
+ else
+ ratings = reviews.map { |review| review.rating }
+ return (ratings.sum.to_f / ratings.length).round(1)
+ end
+ end
+
+ def self.active
+ return self.where(active:true)
+ end
+
+ def self.search(search)
+ products = []
+ self.active.each do |product|
+ products << product if product.name.downcase.include?(search.downcase)
+ end
+ return products
+ end
+end
diff --git a/app/models/review.rb b/app/models/review.rb
new file mode 100644
index 0000000000..aef5039035
--- /dev/null
+++ b/app/models/review.rb
@@ -0,0 +1,24 @@
+class Review < ApplicationRecord
+ validates :rating, numericality: { only_integer: true, greater_than: 0, less_than: 6 }
+ validates :title, presence: true
+ validates_length_of :title, minimum: 1, maximum: 150
+ validates :description, presence: true
+ validates_length_of :description, minimum: 1, maximum: 350
+ validates :product_id, presence: true
+
+ belongs_to :product
+ belongs_to :user, optional: true
+
+ def self.rating_sentiment(rating)
+ if rating > 5 || rating < 1
+ return nil
+ elsif rating < 3
+ return "negative"
+ elsif rating > 3
+ return "positive"
+ elsif rating == 3
+ return "neutral"
+ end
+ end
+
+end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000000..af6831cfb5
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,56 @@
+class User < ApplicationRecord
+ has_many :products
+ has_many :reviews, dependent: :nullify
+
+ validates :uid, uniqueness: true, presence: true
+ validates :merchant_name, uniqueness: true, :allow_nil => true
+ validates_length_of :merchant_name, maximum: 50
+ validates :username, uniqueness: true, presence: true
+ validates :email, uniqueness: true, presence: true, format: { with: /@/, message: "format must be valid." }
+
+ def self.build_from_github(auth_hash)
+ user = User.new
+ user.uid = auth_hash[:uid]
+ user.provider = "github"
+ user.username = auth_hash["info"]["nickname"]
+ user.email = auth_hash["info"]["email"]
+ return user
+ end
+
+ def total_earned
+ all_order_items = self.find_order_items
+ total = 0
+ all_order_items.each do |item|
+ status = item.order.status
+ if status == "paid" || status == "completed"
+ total += item.total
+ end
+ end
+ return total
+ end
+
+ def find_order_items
+ all_products = self.find_products
+ all_order_items = []
+
+ all_products.each do |product|
+ all_order_items << product.order_items
+ end
+ return all_order_items.flatten
+ end
+
+ def find_products
+ all_products = self.products
+ return all_products
+ end
+
+ def filter_order_items(status)
+ order_items = []
+ self.find_products.each do |product|
+ product.order_items.each do |order_item|
+ order_items << order_item if order_item.order.status == status
+ end
+ end
+ return order_items
+ end
+end
diff --git a/app/views/homepages/index.html.erb b/app/views/homepages/index.html.erb
new file mode 100644
index 0000000000..92275549dd
--- /dev/null
+++ b/app/views/homepages/index.html.erb
@@ -0,0 +1,56 @@
+
+ The Sweetest Platform For All Things Fruit!
+
+ Discover Products
+
+ <% random_products = Product.random_products(3) %>
+ <% random_products.each do |product| %>
+
+ <%= product_img_link(product: product, img_url: product.img_url, product_class: "card-img-top")%>
+
+
+ <%= link_to product.name, product_path(product.id) %>
+ <%=currency_format(product.price)%>
+
+
+
+ <% end %>
+
+
+ Deals Under $10
+ <% deals = Product.deals_under(10).first(5) %>
+
+ <% deals.each do |product| %>
+
+ <%= product_img_link(product: product, img_url: product.img_url, product_class: "card-img-top")%>
+
+
+ <%= link_to product.name, product_path(product.id) %>
+ <%=currency_format(product.price)%>
+
+
+
+ <% end %>
+
+
+ <% ["citrus", "berry", "tropical"].each do |theme| %>
+ <%= theme.capitalize %>
+ <% if Category.products_by_category(theme) %>
+ <% products = Category.products_by_category(theme).shuffle.first(3) %>
+
+ <% products.each do |product| %>
+
+ <%= product_img_link(product: product, img_url: product.img_url, product_class: "card-img-top")%>
+
+
+ <%= link_to product.name, product_path(product.id) %>
+ <%=currency_format(product.price)%>
+
+
+
+ <% end %>
+
+ <% end %>
+ <%end%>
+
+
\ No newline at end of file
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
new file mode 100644
index 0000000000..03624a714e
--- /dev/null
+++ b/app/views/layouts/application.html.erb
@@ -0,0 +1,179 @@
+
+
+
+ 🍉fruitsy🍊
+ <%= csrf_meta_tags %>
+ <%= csp_meta_tag %>
+
+
+
+
+
+
+
+
+
+
+
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
+
+
+
+
+
+
+ <%# Fruit Image Links Fixed Nav %>
+
+ <%= fruit_image("48966256972_b25342b8df", "strawberry") %>
+ <%= fruit_image("48966257187_4808931340", "apple") %>
+ <%= fruit_image("48965531768_24fce80958", "orange") %>
+ <%= fruit_image("48966077751_5e88d3a52a", "mango") %>
+ <%= fruit_image("48965531738_c70c61c848", "pineapple") %>
+ <%= fruit_image("48966077756_d140d28fbe", "lemon") %>
+ <%= fruit_image("48965531848_7477ccc902", "banana") %>
+ <%= fruit_image("48966256962_f7655d0227", "watermelon") %>
+ <%= fruit_image("48966077851_25c938e75c", "avocado") %>
+ <%= fruit_image("48965531803_a181744b80", "kiwi") %>
+ <%= fruit_image("48965531838_40b12131ee", "berry") %>
+ <%= fruit_image("48966077781_02aa2abeed", "grapes") %>
+ <%= fruit_image("48965531818_7678560b94", "dragonfruit") %>
+ <%= fruit_image("48966257017_39d2f25373", "peach") %>
+
+
+ <%# Merchant Nav Bar %>
+ <%if session[:user_id]%>
+ <% current_user = User.find_by(id: session[:user_id]) %>
+
+ <%end%>
+
+