diff --git a/.gitignore b/.gitignore
index 6a502e99..c8bb541b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,9 @@
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
+
+# ignore upload files
+/public/uploads
+
+# ignore pravite data
+/config/application.yml
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index fba3b4e0..1969a38e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,3 +38,28 @@ gem 'spring', group: :development
# Use debugger
# gem 'debugger', group: [:development, :test]
+# Devise 提供使用者登入功能
+gem 'devise'
+# 加入 bootstrap css framework
+gem 'bootstrap-sass'
+# Rails forms made easy
+gem 'simple_form'
+
+# 圖片上傳
+gem 'carrierwave'
+# 圖片壓縮 resize
+gem 'mini_magick'
+# 有線狀態機
+gem 'aasm'
+# 線上刷卡
+gem 'stripe'
+
+gem 'settingslogic'
+
+gem 'ransack', github: "activerecord-hackery/ransack", branch: "rails-4.1"
+gem 'will_paginate'
+
+group :development do
+ # 在本機看信
+ gem "letter_opener"
+end
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 53538d9b..39ce760f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,6 +1,19 @@
+GIT
+ remote: git://github.com/activerecord-hackery/ransack.git
+ revision: 625e58cbb5ea5e155464ea2d79497838313352f2
+ branch: rails-4.1
+ specs:
+ ransack (1.2.4)
+ actionpack (>= 4.0)
+ activerecord (>= 4.0)
+ activesupport (>= 4.0)
+ i18n
+ polyamorous (~> 1.0.0)
+
GEM
remote: https://rubygems.org/
specs:
+ aasm (3.2.1)
actionmailer (4.1.0)
actionpack (= 4.1.0)
actionview (= 4.1.0)
@@ -27,8 +40,19 @@ GEM
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
+ addressable (2.3.6)
arel (5.0.1.20140414130214)
+ bcrypt (3.1.7)
+ bcrypt-ruby (3.1.5)
+ bcrypt (>= 3.1.3)
+ bootstrap-sass (3.1.1.1)
+ sass (~> 3.2)
builder (3.2.2)
+ carrierwave (0.10.0)
+ activemodel (>= 3.2.0)
+ activesupport (>= 3.2.0)
+ json (>= 1.7)
+ mime-types (>= 1.16)
coffee-rails (4.0.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
@@ -36,6 +60,11 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.7.0)
+ devise (3.0.2)
+ bcrypt-ruby (~> 3.0)
+ orm_adapter (~> 0.1)
+ railties (>= 3.2.6, < 5)
+ warden (~> 1.2.3)
erubis (2.7.0)
execjs (2.2.0)
hike (1.2.3)
@@ -47,12 +76,21 @@ GEM
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.1)
+ launchy (2.4.2)
+ addressable (~> 2.3)
+ letter_opener (1.2.0)
+ launchy (~> 2.2)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.25.1)
+ mini_magick (3.7.0)
+ subexec (~> 0.2.1)
minitest (5.3.4)
multi_json (1.10.1)
+ orm_adapter (0.5.0)
+ polyamorous (1.0.0)
+ activerecord (>= 3.0)
polyglot (0.3.5)
rack (1.5.2)
rack-test (0.6.2)
@@ -75,6 +113,8 @@ GEM
rake (10.3.2)
rdoc (4.1.1)
json (~> 1.4)
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
sass (3.2.19)
sass-rails (4.0.3)
railties (>= 4.0.0, < 5.0)
@@ -84,6 +124,10 @@ GEM
sdoc (0.4.0)
json (~> 1.8)
rdoc (~> 4.0, < 5.0)
+ settingslogic (2.0.9)
+ simple_form (3.0.2)
+ actionpack (~> 4.0)
+ activemodel (~> 4.0)
spring (1.1.3)
sprockets (2.11.0)
hike (~> 1.2)
@@ -95,6 +139,11 @@ GEM
activesupport (>= 3.0)
sprockets (~> 2.8)
sqlite3 (1.3.9)
+ stripe (1.14.0)
+ json (~> 1.8.1)
+ mime-types (~> 1.25)
+ rest-client (~> 1.4)
+ subexec (0.2.3)
thor (0.19.1)
thread_safe (0.3.4)
tilt (1.4.1)
@@ -108,18 +157,32 @@ GEM
uglifier (2.5.0)
execjs (>= 0.3.0)
json (>= 1.8.0)
+ warden (1.2.3)
+ rack (>= 1.0)
+ will_paginate (3.0.3)
PLATFORMS
ruby
DEPENDENCIES
+ aasm
+ bootstrap-sass
+ carrierwave
coffee-rails (~> 4.0.0)
+ devise
jbuilder (~> 2.0)
jquery-rails
+ letter_opener
+ mini_magick
rails (= 4.1.0)
+ ransack!
sass-rails (~> 4.0.3)
sdoc (~> 0.4.0)
+ settingslogic
+ simple_form
spring
sqlite3
+ stripe
turbolinks
uglifier (>= 1.3.0)
+ will_paginate
diff --git a/app/assets/javascripts/account/orders.js.coffee b/app/assets/javascripts/account/orders.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/account/orders.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/admin/orders.js.coffee b/app/assets/javascripts/admin/orders.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/admin/orders.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/admin/products.js.coffee b/app/assets/javascripts/admin/products.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/admin/products.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index d6925fa4..77431764 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -14,3 +14,4 @@
//= require jquery_ujs
//= require turbolinks
//= require_tree .
+//= require bootstrap
diff --git a/app/assets/javascripts/card_charges.js.coffee b/app/assets/javascripts/card_charges.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/card_charges.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/cart_items.js.coffee b/app/assets/javascripts/cart_items.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/cart_items.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/carts.js.coffee b/app/assets/javascripts/carts.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/carts.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/orders.js.coffee b/app/assets/javascripts/orders.js.coffee
new file mode 100644
index 00000000..49e71100
--- /dev/null
+++ b/app/assets/javascripts/orders.js.coffee
@@ -0,0 +1,35 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
+
+jQuery ($) ->
+ $("#payment-form").submit (event) ->
+ $form = $(this)
+
+ # Disable the submit button to prevent repeated clicks
+ $form.find("button").prop "disabled", true
+ Stripe.createToken $form, stripeResponseHandler
+
+ # Prevent the form from submitting with the default action
+ false
+
+ return
+
+stripeResponseHandler = (status, response) ->
+ $form = $("#payment-form")
+ if response.error
+
+ # Show the errors on the form
+ $form.find(".payment-errors").text response.error.message
+ $form.find("button").prop "disabled", false
+ else
+
+ # token contains id, last4, and card type
+ token = response.id
+
+ # Insert the token into the form so it gets submitted to the server
+ $form.append $(" ").val(token)
+
+ # and submit
+ $form.get(0).submit()
+ return
\ No newline at end of file
diff --git a/app/assets/javascripts/products.js.coffee b/app/assets/javascripts/products.js.coffee
new file mode 100644
index 00000000..24f83d18
--- /dev/null
+++ b/app/assets/javascripts/products.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/account/orders.css.scss b/app/assets/stylesheets/account/orders.css.scss
new file mode 100644
index 00000000..782d07c6
--- /dev/null
+++ b/app/assets/stylesheets/account/orders.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the account::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/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss
new file mode 100644
index 00000000..7ed90c7e
--- /dev/null
+++ b/app/assets/stylesheets/admin/orders.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the admin::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/admin/products.css.scss b/app/assets/stylesheets/admin/products.css.scss
new file mode 100644
index 00000000..0a32fcee
--- /dev/null
+++ b/app/assets/stylesheets/admin/products.css.scss
@@ -0,0 +1,14 @@
+// Place all the styles related to the admin::products controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
+
+.admin{
+ a {
+ margin: 10px 5px;
+ }
+ .buttonsGroup {
+ background-color: #ccc;
+ padding: 0 10px;
+ margin: 5px 0 20px 0;
+ }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css.scss
similarity index 93%
rename from app/assets/stylesheets/application.css
rename to app/assets/stylesheets/application.css.scss
index a443db34..ff72e2a3 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css.scss
@@ -10,6 +10,11 @@
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
* file per style scope.
*
+ *= require bootstrap
*= require_tree .
*= require_self
*/
+
+body {
+ margin-top: 60px;
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/card_charges.css.scss b/app/assets/stylesheets/card_charges.css.scss
new file mode 100644
index 00000000..24adb84c
--- /dev/null
+++ b/app/assets/stylesheets/card_charges.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the card_charges 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/cart_items.css.scss b/app/assets/stylesheets/cart_items.css.scss
new file mode 100644
index 00000000..c8ba3800
--- /dev/null
+++ b/app/assets/stylesheets/cart_items.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the cart_items 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.css.scss b/app/assets/stylesheets/carts.css.scss
new file mode 100644
index 00000000..8b81f431
--- /dev/null
+++ b/app/assets/stylesheets/carts.css.scss
@@ -0,0 +1,15 @@
+// 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/
+$money-red: #c00;
+
+.cart, .checkout, .order, .orderlist {
+ th, .price, .delete, .qt, .timeAgo {
+ text-align: center;
+ }
+ .price, .total {
+ color: $money-red;
+ font-weight: bold;
+ font-size: 1.2em;
+ }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/orders.css.scss b/app/assets/stylesheets/orders.css.scss
new file mode 100644
index 00000000..3b0428a9
--- /dev/null
+++ b/app/assets/stylesheets/orders.css.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.css.scss b/app/assets/stylesheets/products.css.scss
new file mode 100644
index 00000000..21dc5ae0
--- /dev/null
+++ b/app/assets/stylesheets/products.css.scss
@@ -0,0 +1,24 @@
+// 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/
+
+$text-color: #888;
+$title-color: #555;
+
+.products{
+ padding: 15px;
+ margin-bottom: 10px;
+ border-bottom: 1px solid #ccc;
+ h3 a, h4{
+ color: $title-color;
+ }
+ h4{
+ margin-top: 55px;
+ }
+ p{
+ color: $text-color;
+ }
+ .addToCart {
+ margin: 15px 0;
+ }
+}
\ No newline at end of file
diff --git a/app/controllers/account/orders_controller.rb b/app/controllers/account/orders_controller.rb
new file mode 100644
index 00000000..0480b30c
--- /dev/null
+++ b/app/controllers/account/orders_controller.rb
@@ -0,0 +1,7 @@
+class Account::OrdersController < ApplicationController
+
+ def index
+ @orders = current_user.orders
+ end
+
+end
diff --git a/app/controllers/admin/orders_controller.rb b/app/controllers/admin/orders_controller.rb
new file mode 100644
index 00000000..bcb44093
--- /dev/null
+++ b/app/controllers/admin/orders_controller.rb
@@ -0,0 +1,41 @@
+class Admin::OrdersController < ApplicationController
+
+ before_filter :find_order, :except => [:index]
+
+ def index
+ # TODO Pagenavi
+ @orders = Order.all.limit(50)
+ end
+
+ def show
+ @order_info = @order.info
+ @order_items = @order.items
+ end
+
+ def ship
+ @order.ship!
+ redirect_to :back
+ end
+
+ def shipped
+ @order.deliver!
+ redirect_to :back
+ end
+
+ def cancel
+ @order.cancel_order!
+ redirect_to :back
+ end
+
+ def return
+ @order.return_good!
+ redirect_to :back
+ end
+
+ protected
+
+ def find_order
+ @order = Order.find_by_token(params[:id])
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/admin/products_controller.rb b/app/controllers/admin/products_controller.rb
new file mode 100644
index 00000000..bacefb59
--- /dev/null
+++ b/app/controllers/admin/products_controller.rb
@@ -0,0 +1,62 @@
+class Admin::ProductsController < ApplicationController
+
+ # 使用者必須先登入
+ before_action :authenticate_user!
+
+ # 使用者的身份必須是 admin
+ # admin_required 在 applicationController 裡定義
+ before_action :admin_required
+
+ def new
+ @product = Product.new
+ # ???
+ @photo = @product.photos.new
+ end
+
+ def create
+ @product = Product.new(product_params)
+
+ if @product.save
+ redirect_to admin_products_path
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @product = Product.find(params[:id])
+ end
+
+ def update
+
+ @product = Product.find(params[:id])
+
+ if @product.update(product_params)
+ redirect_to admin_products_path
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @product = Product.find(params[:id])
+ @product.destroy
+ redirect_to admin_products_path
+ end
+
+ def index
+ @products = Product.all
+ end
+
+ def show
+ @product = Product.find(params[:id])
+ end
+
+ private
+
+ def product_params
+ # :nested_attributes => [:關聯 Model 的欄位]
+ params.require(:product).permit(:title, :description, :quantity, :price, :photos_attributes => [:image])
+ end
+
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d83690e1..18b04976 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -2,4 +2,34 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+
+ def admin_required
+ # 目前的使用者身份是否為 admin?
+ # admin? 在 user.rb 定義
+ current_user.admin?
+ end
+
+ # 宣告 current_cart 是一個 controller 級的 helper
+ helper_method :current_cart
+
+ def current_cart
+ # @current_cart = cuttent_cart or find_cart
+ # ?
+ @current_cart ||= find_cart
+ end
+
+ def find_cart
+
+ cart = Cart.find_by(id: session[:cart_id])
+
+ unless cart.present?
+ # 如果 session 內沒有 cart 就生一個
+ cart = Cart.create
+ end
+
+ session[:cart_id] = cart.id
+ # return cart to current_cart
+ cart
+ end
+
end
diff --git a/app/controllers/card_charges_controller.rb b/app/controllers/card_charges_controller.rb
new file mode 100644
index 00000000..41b4dadc
--- /dev/null
+++ b/app/controllers/card_charges_controller.rb
@@ -0,0 +1,22 @@
+class CardChargesController < ApplicationController
+
+ before_action :authenticate_user!
+
+ def create
+
+ @order = current_user.orders.find_by_token(params[:order_id])
+ @amount = @order.total * 100 # in cents
+ @card_info = params[:stripeToken]
+
+ ChargesByCardService.new(current_user, @order, @amount, @card_info).charge_by_card!
+
+ redirect_to order_path(@order.token), :notice => "成功完成付款"
+ # 寄信
+ PaymentMailer.notify_payment_completed(@order).deliver
+
+ rescue Stripe::CardError => e
+ flash[:error] = e.message
+ render "orders/pay_with_credit_card"
+ end
+
+end
diff --git a/app/controllers/cart_items_controller.rb b/app/controllers/cart_items_controller.rb
new file mode 100644
index 00000000..6496add2
--- /dev/null
+++ b/app/controllers/cart_items_controller.rb
@@ -0,0 +1,25 @@
+class CartItemsController < ApplicationController
+
+ def update
+ @cart = current_cart
+ @item = @cart.cart_items.find(params[:id])
+ @item.update(item_params)
+
+ redirect_to carts_path
+ end
+
+ def destroy
+ @cart = current_cart
+ @item = @cart.cart_items.find(params[:id])
+ @item.destroy
+ flash[:notice] = "已經將商品移出購物車"
+ redirect_to :back
+ end
+
+ private
+
+ def item_params
+ params.require(:cart_item).permit(:quantity)
+ end
+
+end
diff --git a/app/controllers/carts_controller.rb b/app/controllers/carts_controller.rb
new file mode 100644
index 00000000..d45e1885
--- /dev/null
+++ b/app/controllers/carts_controller.rb
@@ -0,0 +1,14 @@
+class CartsController < ApplicationController
+
+ # 必須登入才能結賬
+ before_action :authenticate_user!, :only => [:checkout]
+
+ def index
+ end
+
+ def checkout
+ @order = current_user.orders.build
+ @info = @order.build_info
+ end
+
+end
diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb
new file mode 100644
index 00000000..c1e87507
--- /dev/null
+++ b/app/controllers/orders_controller.rb
@@ -0,0 +1,35 @@
+class OrdersController < ApplicationController
+
+ before_action :authenticate_user!
+
+ def create
+ @order = current_user.orders.build(order_params)
+
+ if @order.save
+
+ OrderPlacingService.new(current_cart, @order).place_order!
+ # 產生 token 保密訂單網址
+ redirect_to order_path(@order.token)
+ else
+ # 跨 controller
+ render "carts/checkout"
+ end
+ end
+
+ def show
+ @order = current_user.orders.find_by_token(params[:id])
+ @order_info = @order.info
+ @order_items = @order.items
+ end
+
+ def pay_with_credit_card
+ @order = current_user.orders.find_by_token(params[:id])
+ end
+
+ private
+
+ def order_params
+ params.require(:order).permit(:info_attributes => [:billing_name, :billing_address, :shipping_name, :shipping_address])
+ end
+
+end
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
new file mode 100644
index 00000000..dec7232e
--- /dev/null
+++ b/app/controllers/products_controller.rb
@@ -0,0 +1,61 @@
+class ProductsController < ApplicationController
+
+ before_filter :validate_search_key, :only => [:search]
+
+ def index
+ # 依 ID 降冪排序(最新的產品在最上面)
+ @products = Product.order("id DESC")
+ end
+
+ def show
+ @product = Product.find(params[:id])
+ end
+
+ def search
+ if @quert_string.present?
+ search_result = Product.ransack(@search_criteria).result(:distinct => true)
+ @products = search_result.paginate(:page => params[:page], :per_page => 20)
+ end
+ end
+
+ def add_to_cart
+
+ @product = Product.find(params[:id])
+
+ # 如果商品不存在購物車
+ #(current_cart 要在 applicationController 自己造)用 helper_method :current_cart
+ # 這樣就能在這裡 (controller) 和 view 使用 current_cart 了
+ if !current_cart.items.include?(@product)
+ if @product.quantity <= 0
+ flash[:warning] = "很抱歉,此商品已經沒有庫存,請下次再買。"
+ else
+ # 把商品加到購物車 (cart model)
+ # def add_product_to_cart(product)
+ # items << product
+ # end
+ # 更新 add_product_to_cart method 新增數量,所以這邊預設將 1 個商品加入購物車
+ current_cart.add_product_to_cart(@product, 1)
+ flash[:notice] = "已成功將 #{@product.title} 加入購物車"
+ end
+ else
+ # 再加一個商品到購物車
+ current_cart.add_onemore_product_to_cart(@product, 1)
+ flash[:notice] = "已增加一件 #{@product.title} 進您的購物車"
+ end
+
+ redirect_to :back
+
+ end
+
+ protected
+
+ def validate_search_key
+ @query_string = params[:q].gsub(/\\|\'|\/|\?/, "") if params[:q].present?
+ @search_criteria = search_criteria(@query_string)
+ end
+
+ def search_criteria(query_string)
+ { :title_cont => query_string }
+ end
+
+end
diff --git a/app/helpers/account/orders_helper.rb b/app/helpers/account/orders_helper.rb
new file mode 100644
index 00000000..6f690c4e
--- /dev/null
+++ b/app/helpers/account/orders_helper.rb
@@ -0,0 +1,2 @@
+module Account::OrdersHelper
+end
diff --git a/app/helpers/admin/orders_helper.rb b/app/helpers/admin/orders_helper.rb
new file mode 100644
index 00000000..76b09744
--- /dev/null
+++ b/app/helpers/admin/orders_helper.rb
@@ -0,0 +1,7 @@
+module Admin::OrdersHelper
+
+ def render_order_options_for_admin(order)
+ render :partial => "admin/orders/state_option", :locals => { :order => order}
+ end
+
+end
diff --git a/app/helpers/admin/products_helper.rb b/app/helpers/admin/products_helper.rb
new file mode 100644
index 00000000..977a242f
--- /dev/null
+++ b/app/helpers/admin/products_helper.rb
@@ -0,0 +1,2 @@
+module Admin::ProductsHelper
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de6be794..fdf86a68 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,2 +1,21 @@
module ApplicationHelper
+
+ def notice_message
+ alert_types = { :notice => :success, :alert => :danger }
+
+ close_button_options = { :class => "close", "data-dismiss" => "alert", "aria-hidden" => true }
+ close_button = content_tag(:button, "×", close_button_options)
+
+ alerts = flash.map do |type, message|
+ alert_content = close_button + message
+
+ alert_type = alert_types[type.to_sym] || type
+ alert_class = "alert alert-#{alert_type} alert-dismissable"
+
+ content_tag(:div, alert_content, :class => alert_class)
+ end
+
+ alerts.join("\n").html_safe
+ end
+
end
diff --git a/app/helpers/card_charges_helper.rb b/app/helpers/card_charges_helper.rb
new file mode 100644
index 00000000..5c18c543
--- /dev/null
+++ b/app/helpers/card_charges_helper.rb
@@ -0,0 +1,2 @@
+module CardChargesHelper
+end
diff --git a/app/helpers/cart_items_helper.rb b/app/helpers/cart_items_helper.rb
new file mode 100644
index 00000000..f30f6834
--- /dev/null
+++ b/app/helpers/cart_items_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 00000000..66cf684f
--- /dev/null
+++ b/app/helpers/carts_helper.rb
@@ -0,0 +1,11 @@
+module CartsHelper
+
+ def cart_items_count(cart)
+ cart.cart_items.count
+ end
+
+ def render_cart_total_price(current_cart)
+ current_cart.total_price
+ end
+
+end
diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb
new file mode 100644
index 00000000..9aec5cdf
--- /dev/null
+++ b/app/helpers/orders_helper.rb
@@ -0,0 +1,7 @@
+module OrdersHelper
+
+def render_order_state(order)
+ t("orders.order_state.#{order.aasm_state}")
+end
+
+end
diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb
new file mode 100644
index 00000000..f804f45f
--- /dev/null
+++ b/app/helpers/products_helper.rb
@@ -0,0 +1,27 @@
+module ProductsHelper
+
+ def render_product_photo(photo, size = "thumb")
+
+ if photo.present?
+ image_url = photo.image.send(size).url
+ else
+
+ case size
+ when :medium
+ volume = "300x300"
+ else
+ volume = "200x200"
+ end
+
+ image_url = "http://placehold.it/#{volume}&text=No Pic"
+ end
+
+ image_tag(image_url, :class => "thumbnail img-responsive")
+
+ end
+
+ def render_product_name(product)
+ product.title
+ end
+
+end
\ No newline at end of file
diff --git a/app/mailers/order_mailer.rb b/app/mailers/order_mailer.rb
new file mode 100644
index 00000000..db29c953
--- /dev/null
+++ b/app/mailers/order_mailer.rb
@@ -0,0 +1,12 @@
+class OrderMailer < ActionMailer::Base
+ default from: "service@artstore.com"
+
+ def notify_order_placed(order)
+ @order = order
+ @user = order.user
+ @order_items = @order.items
+ @order_info = @order.info
+
+ mail(:to => @user.email, :subject => "[ArtStore] 感謝您完成本次下單,以下是您這次的購物明細 #{order.token}")
+ end
+end
diff --git a/app/mailers/payment_mailer.rb b/app/mailers/payment_mailer.rb
new file mode 100644
index 00000000..60d79ece
--- /dev/null
+++ b/app/mailers/payment_mailer.rb
@@ -0,0 +1,13 @@
+class PaymentMailer < ActionMailer::Base
+ default from: "service@artstore.com"
+
+ def notify_payment_completed(order)
+ @order = order
+ @user = order.user
+ @order_items = @order.items
+ @order_info = @order.info
+
+ mail(:to => @user.email, :subject => "[ArtStore] 感謝您的購買,您的訂單已經完成信用卡結賬手續,我們將盡快為您進行出貨 #{order.token}")
+ end
+
+end
diff --git a/app/models/cart.rb b/app/models/cart.rb
new file mode 100644
index 00000000..92c4ca17
--- /dev/null
+++ b/app/models/cart.rb
@@ -0,0 +1,32 @@
+class Cart < ActiveRecord::Base
+
+ has_many :cart_items, :dependent => :destroy
+ # 透過 cart_items 和 items 作關聯,根據 cart_items 中的 product
+ has_many :items, :through => :cart_items, :source => :product
+
+ def add_product_to_cart(product, qt)
+ item = cart_items.new
+ item.product = product
+ item.quantity = qt
+ item.save
+ end
+
+ def add_onemore_product_to_cart(product, qt)
+ # 再加一個商品到購物車
+ current_item = cart_items.find_by_product_id(product)
+ current_item.quantity += qt
+ current_item.save
+ end
+
+ def total_price
+ cart_items.inject(0) {|sum, item| sum + item.product.price * item.quantity }
+ end
+
+ # 清空購物車
+ def clear!
+ # delete_all 是內建的 method,可一次清空
+ # destroy_all 會一個一個 row 刪(?)
+ cart_items.delete_all
+ end
+
+end
diff --git a/app/models/cart_item.rb b/app/models/cart_item.rb
new file mode 100644
index 00000000..29642d20
--- /dev/null
+++ b/app/models/cart_item.rb
@@ -0,0 +1,6 @@
+class CartItem < ActiveRecord::Base
+
+ belongs_to :cart
+ belongs_to :product
+
+end
diff --git a/app/models/concerns/tokenable.rb b/app/models/concerns/tokenable.rb
new file mode 100644
index 00000000..c2f1a9a4
--- /dev/null
+++ b/app/models/concerns/tokenable.rb
@@ -0,0 +1,13 @@
+module Tokenable
+
+ extend ActiveSupport::Concern
+
+ included do
+ before_create :generate_token
+ end
+
+ def generate_token
+ self.token = SecureRandom.uuid
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/order.rb b/app/models/order.rb
new file mode 100644
index 00000000..c52a4728
--- /dev/null
+++ b/app/models/order.rb
@@ -0,0 +1,72 @@
+class Order < ActiveRecord::Base
+
+ belongs_to :user
+ has_many :items, :class_name => "OrderItem", :dependent => :destroy
+ has_one :info, :class_name => "OrderInfo", :dependent => :destroy
+
+ accepts_nested_attributes_for :info
+
+ include Tokenable
+
+ def build_item_cache_from_cart(cart)
+ cart.items.each do |cart_item|
+ # https://github.com/rails/rails/blob/959fb8ea651fa6638aaa7caced20d921ca2ea5c1/activerecord/lib/active_record/relation.rb#L84
+ # build method is a alias of 'new'
+ item = items.build
+ item.product_name = cart_item.title
+ item.quantity = 1
+ item.price = cart_item.price
+ item.save
+ end
+ end
+
+ def caculate_total!(current_cart)
+ self.total = current_cart.total_price
+ self.save
+ end
+
+ def set_payment_with!(method)
+ self.update_column(:payment_method, method)
+ end
+
+ def pay!
+ self.update_column(:paid, true)
+ end
+
+ include AASM
+
+ aasm do
+ # 預設為 order placed
+ # 結賬後狀態轉為 paid
+ state :order_placed, :initial => true
+ # If you want to make sure a depending action happens only after the transaction is committed, use the after_commit callback
+ # 確認狀態變更後 => pay! (update column paid to t)
+ state :paid, :after_commit => :pay!
+ event :make_payment do
+ transitions :from => :order_placed, :to => :paid
+ end
+
+ # 出貨後狀態轉為 shipping
+ state :shipping
+ event :ship do
+ transitions :from => :paid, :to => :shipping
+ end
+
+ # 到貨後狀態轉為 shipped
+ state :shipped
+ event :deliver do
+ transitions :from => :shipping, :to => :shipped
+ end
+
+ state :order_cancelled
+ event :cancel_order do
+ transitions :from => [:order_placed, :paid], :to => :order_cancelled
+ end
+
+ state :good_returned
+ event :return_good do
+ transitions :from => :shipped, :to => :good_returned
+ end
+ end
+
+end
diff --git a/app/models/order_info.rb b/app/models/order_info.rb
new file mode 100644
index 00000000..6a99da97
--- /dev/null
+++ b/app/models/order_info.rb
@@ -0,0 +1,5 @@
+class OrderInfo < ActiveRecord::Base
+ belongs_to :order
+
+ validates_presence_of :billing_name, :billing_address, :shipping_name, :shipping_address
+end
diff --git a/app/models/order_item.rb b/app/models/order_item.rb
new file mode 100644
index 00000000..79691641
--- /dev/null
+++ b/app/models/order_item.rb
@@ -0,0 +1,3 @@
+class OrderItem < ActiveRecord::Base
+ belongs_to :order
+end
diff --git a/app/models/photo.rb b/app/models/photo.rb
new file mode 100644
index 00000000..0f734e9c
--- /dev/null
+++ b/app/models/photo.rb
@@ -0,0 +1,7 @@
+class Photo < ActiveRecord::Base
+
+ belongs_to :product
+
+ mount_uploader :image, ImageUploader
+
+end
diff --git a/app/models/product.rb b/app/models/product.rb
new file mode 100644
index 00000000..5272c4fb
--- /dev/null
+++ b/app/models/product.rb
@@ -0,0 +1,17 @@
+class Product < ActiveRecord::Base
+
+ has_many :photos
+
+ # accepts_nested_attributes_for :MODEL
+ # 可以直接更新關聯 MODEL 的資料
+ accepts_nested_attributes_for :photos
+
+ # 驗證表單是否填妥
+ validates_presence_of :title, :quantity
+
+ # 定義最新的照片為
+ def default_photo
+ photos.first
+ end
+
+end
diff --git a/app/models/settings.rb b/app/models/settings.rb
new file mode 100644
index 00000000..e9a7e9db
--- /dev/null
+++ b/app/models/settings.rb
@@ -0,0 +1,4 @@
+class Settings < Settingslogic
+ source "#{Rails.root}/config/application.yml"
+ namespace Rails.env
+end
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 00000000..1ac59199
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,14 @@
+class User < ActiveRecord::Base
+ # Include default devise modules. Others available are:
+ # :token_authenticatable, :confirmable,
+ # :lockable, :timeoutable and :omniauthable
+ devise :database_authenticatable, :registerable,
+ :recoverable, :rememberable, :trackable, :validatable
+
+ has_many :orders
+
+ def admin?
+ is_admin
+ end
+
+end
diff --git a/app/services/charges_by_card_service.rb b/app/services/charges_by_card_service.rb
new file mode 100644
index 00000000..c333eadd
--- /dev/null
+++ b/app/services/charges_by_card_service.rb
@@ -0,0 +1,29 @@
+class ChargesByCardService
+ def initialize(user, order, amount, card_info)
+ @user = user
+ @order = order
+ @amount = amount
+ @card_info = card_info
+ end
+
+ def charge_by_card!
+ Stripe.api_key = Settings.stripe.secret_key
+
+ customer = Stripe::Customer.create(
+ :email => @user.email,
+ :card => @card_info
+ )
+
+
+ charge = Stripe::Charge.create(
+ :customer => customer.id,
+ :amount => @amount,
+ :description => @order.token ,
+ :currency => 'twd'
+ )
+
+ @order.set_payment_with!("credit_card")
+ @order.make_payment!
+ end
+
+end
\ No newline at end of file
diff --git a/app/services/order_placing_service.rb b/app/services/order_placing_service.rb
new file mode 100644
index 00000000..37993c50
--- /dev/null
+++ b/app/services/order_placing_service.rb
@@ -0,0 +1,13 @@
+class OrderPlacingService
+ def initialize(cart,order)
+ @order = order
+ @cart = cart
+ end
+
+ def place_order!
+ @order.build_item_cache_from_cart(@cart)
+ @order.caculate_total!(@cart)
+ @cart.clear!
+ OrderMailer.notify_order_placed(@order).deliver
+ end
+end
\ No newline at end of file
diff --git a/app/uploaders/image_uploader.rb b/app/uploaders/image_uploader.rb
new file mode 100644
index 00000000..e9c15cb6
--- /dev/null
+++ b/app/uploaders/image_uploader.rb
@@ -0,0 +1,61 @@
+# encoding: utf-8
+
+class ImageUploader < CarrierWave::Uploader::Base
+
+ # Include RMagick or MiniMagick support:
+ # include CarrierWave::RMagick
+ include CarrierWave::MiniMagick
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ def store_dir
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ end
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ # def default_url
+ # # For Rails 3.1+ asset pipeline compatibility:
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
+ #
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
+ # end
+
+ # Process files as they are uploaded:
+ # process :scale => [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+ # Create different versions of your uploaded files:
+
+ # 將最長邊縮至 1024px
+ process :resize_to_fit => [1024, 1024]
+
+ # 製作 250 x 250 的 1:1 方形縮圖
+ version :thumb do
+ process :resize_to_fill => [250, 250]
+ end
+
+ # 製作最長邊 500 的中等尺寸
+ version :mid do
+ process :resize_to_fit => [500, 500]
+ end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ # def extension_white_list
+ # %w(jpg jpeg gif png)
+ # end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # "something.jpg" if original_filename
+ # end
+
+end
diff --git a/app/views/account/orders/index.html.erb b/app/views/account/orders/index.html.erb
new file mode 100644
index 00000000..0b29ca76
--- /dev/null
+++ b/app/views/account/orders/index.html.erb
@@ -0,0 +1,19 @@
+
+
訂單列表
+
+
+
+ 訂單序號
+ 訂單時間
+
+
+
+ <% @orders.each do |order| %>
+
+ <%= link_to order.token, order_path(order.token) %>
+ <%= time_ago_in_words(order.created_at) %> 之前
+
+ <% end %>
+
+
+
\ No newline at end of file
diff --git a/app/views/admin/orders/_state_option.html.erb b/app/views/admin/orders/_state_option.html.erb
new file mode 100644
index 00000000..2e3857c4
--- /dev/null
+++ b/app/views/admin/orders/_state_option.html.erb
@@ -0,0 +1,21 @@
+
+ <% case order.aasm_state %>
+ <% when "order_placed" %>
+ <%= link_to("取消訂單", cancel_admin_order_path(order.token) , :class => "btn btn-default btn-sm", :method => :post) %>
+
+ <% when "paid" %>
+ <%= link_to("取消訂單", cancel_admin_order_path(order.token) , :class => "btn btn-default btn-sm", :method => :post) %>
+ <%= link_to("出貨", ship_admin_order_path(order.token) , :class => "btn btn-default btn-sm", :method => :post) %>
+ <% when "shipping" %>
+ <%= link_to("設為已出貨", shipped_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post) %>
+ <% when "shipped" %>
+ <%= link_to("退貨", return_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post) %>
+
+ <% when "order_cancelled" %>
+ 訂單已取消
+ <% when "good_returned" %>
+ 已退貨
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/admin/orders/index.html.erb b/app/views/admin/orders/index.html.erb
new file mode 100644
index 00000000..2acc45c6
--- /dev/null
+++ b/app/views/admin/orders/index.html.erb
@@ -0,0 +1,39 @@
+
+
訂單列表
+
+
+
+ 訂單序號
+ 訂單時間
+ 使用者帳號
+ 付款方式
+ 訂單狀態
+ 訂單選項
+
+
+
+ <% @orders.each do |order| %>
+
+ <%= link_to order.token, admin_order_path(order.token) %>
+ <%= time_ago_in_words(order.created_at) %> 之前
+ <%= order.user.email %>
+ <%= order.payment_method %>
+ <%= render_order_state(order) %>
+
+ <% case order.aasm_state %>
+ <% when "order_placed" %>
+ <%= link_to "取消訂單", cancel_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post %>
+ <% when "paid" %>
+ <%= link_to "出貨", ship_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post %>
+ <%= link_to "取消訂單", cancel_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post %>
+ <% when "shipping" %>
+ <%= link_to "已到貨", shipped_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post %>
+ <% when "shipped" %>
+ <%= link_to "退貨", return_admin_order_path(order.token), :class => "btn btn-default btn-sm", :method => :post %>
+ <% end %>
+
+
+ <% end %>
+
+
+
\ No newline at end of file
diff --git a/app/views/admin/orders/show.html.erb b/app/views/admin/orders/show.html.erb
new file mode 100644
index 00000000..e19ff858
--- /dev/null
+++ b/app/views/admin/orders/show.html.erb
@@ -0,0 +1,89 @@
+
+
+
+
訂單明細
+
+ <%= render_order_options_for_admin(@order) %>
+
+
+
+
+ 商品明細
+ 單價
+ 數量
+
+
+
+ <% @order_items.each do |order_item| %>
+
+
+ <%= order_item.product_name %>
+
+ <%= order_item.price %>
+ <%= order_item.quantity %>
+
+ <% end %>
+
+
+
+
+
+
+ 總計 <%= @order.total %> NTD
+
+
+
+
+
+
寄送資訊
+
+
+
+
+ 訂購人
+
+
+
+
+
+ <%= @order_info.billing_name %> - <%= @order_info.billing_address %>
+
+
+
+
+ 訂購人
+
+
+
+
+
+ <%= @order_info.billing_name %> - <%= @order_info.billing_address %>
+
+
+
+
+
+
+
+
+ <% if !@order.paid? %>
+
+
+ <%= link_to("以信用卡付款", pay_with_credit_card_order_path(@order.token), :class => "btn btn-primary btn-lg") %>
+
+ <%= link_to("以 ATM 付款", "#", :class => "btn btn-primary btn-lg") %>
+
+
+ <% else %>
+
+ 此訂單已完成付款
+
+ <% end %>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/admin/products/edit.html.erb b/app/views/admin/products/edit.html.erb
new file mode 100644
index 00000000..9375e090
--- /dev/null
+++ b/app/views/admin/products/edit.html.erb
@@ -0,0 +1,10 @@
+<%= simple_form_for [:admin, @product] do |f| %>
+ <%= f.input :title, label: '標題' %>
+ <%= f.input :description, label: '產品敘述' %>
+ <%= f.input :quantity, label: '數量' %>
+ <%= f.simple_fields_for :photos do |c| %>
+ <%= c.input :image , :as => :file %>
+ <% end %>
+ <%= f.input :price, label: '價格' %>
+ <%= f.submit "Submit", :disable_with => 'Submiting...' %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/admin/products/index.html.erb b/app/views/admin/products/index.html.erb
new file mode 100644
index 00000000..27c40be9
--- /dev/null
+++ b/app/views/admin/products/index.html.erb
@@ -0,0 +1,17 @@
+
+
+ <%= link_to("新增產品", new_admin_product_path, :class => "btn btn-primary btn-sm pull-right") %>
+ <%= link_to("查看客戶訂單", admin_orders_path, :class => "btn btn-primary btn-sm pull-right") %>
+
+
+ <% @products.each do |product| %>
+
+ <%= link_to(product.title, admin_products_path(product)) %> | <%= link_to "EDIT", edit_admin_product_path(product) %> | <%= link_to "刪除", admin_product_path(product), :method => :delete, :confirm => "確定刪除產品?" %>
+
+ <%= render_product_photo(product.default_photo) %>
+
+
+
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/admin/products/new.html.erb b/app/views/admin/products/new.html.erb
new file mode 100644
index 00000000..a17e0322
--- /dev/null
+++ b/app/views/admin/products/new.html.erb
@@ -0,0 +1,36 @@
+
+<%= simple_form_for [:admin, @product] do |f| %>
+ <%= f.input :title, label: '標題' %>
+ <%= f.input :description, label: '產品敘述' %>
+ <%= f.input :quantity, label: '數量' %>
+
+ <%= f.simple_fields_for :photos do |c| %>
+ <%= c.input :image , :as => :file %>
+ <% end %>
+ <%= f.input :price, label: '價格' %>
+ <%= f.submit "Submit", :disable_with => 'Submiting...' %>
+<% end %>
+
+
+
\ No newline at end of file
diff --git a/app/views/carts/checkout.html.erb b/app/views/carts/checkout.html.erb
new file mode 100644
index 00000000..5a7af042
--- /dev/null
+++ b/app/views/carts/checkout.html.erb
@@ -0,0 +1,76 @@
+
+
+
+
購物明細
+
+
+
+
+ 商品名稱
+ 單價
+ 數量
+
+
+
+ <% current_cart.cart_items.each do |item| %>
+
+
+
+ <%= link_to(item.product.title, product_path(item.product)) %>
+
+ <%= item.product.price %>
+ <%= item.quantity %>
+
+ <% end %>
+
+
+
+
+
+
+ 總計 <%= render_cart_total_price(current_cart) %> NTD
+
+
+
+
+
+
訂單資訊
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/carts/index.html.erb b/app/views/carts/index.html.erb
new file mode 100644
index 00000000..e81d0bbc
--- /dev/null
+++ b/app/views/carts/index.html.erb
@@ -0,0 +1,46 @@
+
+
+ <%= link_to("清空購物車", "#", :class => "pull-right btn btn-primary btn-sm") %>
+
購物車
+
+
+
+
+ 商品資訊
+ 單價
+ 數量
+ 刪除商品
+
+
+
+ <% current_cart.cart_items.each do |item| %>
+
+ <%= render_product_photo(item.product.default_photo) %>
+ <%= link_to(item.product.title, product_path(item.product)) %>
+ <%= item.product.price %>
+
+ <%= simple_form_for item, :url => cart_item_path(item), defaults: {label: false} do |f| %>
+ <%= f.input :quantity, :value => item.quantity, :class => "col-md-10" %>
+ <%= f.submit "變更數量", :disable_with => 'Submiting...' %>
+ <% end -%>
+
+ <%= link_to("x", cart_item_path(item), :method => :delete, :class => "btn btn-xs btn-default") %>
+
+ <% end %>
+
+
+
+
+
+ 總計 <%= render_cart_total_price(current_cart) %> NTD
+
+
+
+
+
+ <%= link_to("確認結賬", checkout_carts_path , :method => :post , :class => "btn btn-lg btn-danger pull-right") %>
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/common/_footer.html.erb b/app/views/common/_footer.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/common/_navbar.html.erb b/app/views/common/_navbar.html.erb
new file mode 100644
index 00000000..6558e45a
--- /dev/null
+++ b/app/views/common/_navbar.html.erb
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= link_to carts_path do %>
+ 購物車 <%= cart_items_count(current_cart) %>
+ <% end %>
+
+ <% if !current_user %>
+ <%= link_to "註冊", new_user_registration_path %>
+ <%= link_to "登入", new_user_session_path %>
+ <% else %>
+
+ 會員選單
+
+
+ <% end %>
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index da9769de..8f1c3f7f 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -8,7 +8,18 @@
-<%= yield %>
+
+<%= render :partial => "common/navbar" %>
+
+
+
+ <%= notice_message %>
+
+ <%= yield %>
+
+
+
+<%= render :partial => "common/footer" %>