]> _ Git - pmi.git/commitdiff
Cart and quote functionality. WIP #2769 @9
authorStephen Cameron <stephen@cubedesigners.com>
Tue, 13 Aug 2019 19:21:30 +0000 (21:21 +0200)
committerStephen Cameron <stephen@cubedesigners.com>
Tue, 13 Aug 2019 19:21:30 +0000 (21:21 +0200)
17 files changed:
.gitignore
app/Http/Controllers/AjaxController.php
app/Models/Product.php
app/Templates/Base.php
public/images/tick.svg [new file with mode: 0644]
resources/js/app.js
resources/js/components/Cart.vue
resources/js/components/CartAdd.vue [new file with mode: 0644]
resources/js/components/CartItem.vue
resources/styles/components/buttons.styl
resources/views/components/text-block.blade.php
resources/views/layouts/app.blade.php
resources/views/pages/cart.blade.php
resources/views/pages/product-detail.blade.php
resources/views/pages/products.blade.php
resources/views/partials/form.blade.php
yarn.lock

index eb84a4e89277d2be8b818f1650c406df5fd468e1..8931bb7418a7fb37657dd0b5856cb3d710ad713e 100644 (file)
@@ -4,6 +4,7 @@
 /public/mix-manifest.json
 /public/css
 /public/js
+/public/vendor
 /storage
 /vendor
 /.idea
index b92abf6675484a4c295fa3186a799994d0abeb54..ab07625f0d7aa23f7df037bee160ae3c8176d802 100644 (file)
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
 
 
 use App\Models\Page;
+use App\Models\Product;
 use Cubist\Backpack\app\Http\Controllers\CubistFrontController;
 use Cubist\Backpack\app\Magic\PageData;
 use Illuminate\Http\Request;
@@ -89,6 +90,47 @@ class AjaxController extends CubistFrontController
         });
     }
 
+    public function cart(Request $request) {
+
+        $request->validate([
+            'action' => 'required|string', // add/update/delete
+            'id' => 'required|numeric',
+            'quantity' => 'numeric',
+        ]);
+
+        $id = $request->input('id');
+        $quantity = $request->input('quantity', 1);
+
+        // Cart items stored as an array with IDs as keys and quantities as values
+        // Get existing session or an empty array
+        $cart_items = $request->session()->get('cart_items', []);
+
+        switch($request->input('action')) {
+
+            case 'add':
+                // If the item already exists in the cart, increment the quantity
+                if (isset($cart_items[$id])) {
+                    $cart_items[$id] += $quantity;
+                } else {
+                    $cart_items[$id] = $quantity;
+                }
+                break;
+
+            case 'update':
+                $cart_items[$id] = $quantity;
+                break;
+
+            case 'delete':
+                unset($cart_items[$id]);
+                break;
+        }
+
+        // Save back to the session
+        $request->session()->put('cart_items', $cart_items);
+
+        return Product::getCartData();
+    }
+
     // Subscribe to newsletter via MailChimp API
     public function newsletter(Request $request)
     {
index 8fa9f25cdb92e210fa52b205f157029a5013aa38..904badd72a2192338c9bd38400478481e9dd2b55 100644 (file)
@@ -28,6 +28,12 @@ class Product extends CubistMagicModel
         parent::__construct($attributes);
     }
 
+    // Define relationship with Product Types
+    public function type()
+    {
+        return $this->belongsTo('App\Models\ProductType', 'product_type');
+    }
+
     public function setFields()
     {
         parent::setFields();
@@ -294,4 +300,56 @@ class Product extends CubistMagicModel
         }
         return $res;
     }
+
+
+    /**
+     * Custom accessor to return fallback image
+     * by accessing $product->image_fallback...
+     */
+    public function getImageFallbackAttribute() {
+        return asset('images/product-details/product-placeholder.svg');
+    }
+
+    /**
+     * Custom accessor to return main product image (or fallback)
+     * by accessing $product->image...
+     */
+    public function getImageAttribute() {
+        if ($this->images) {
+
+            $image = $this->getFirstMediaUrl($this->images);
+
+            if ($image) {
+                return $image;
+            }
+        }
+
+        return $this->image_fallback;
+    }
+
+
+    /**
+     * Fetch selected product data for use in cart Vue component
+     * @return array
+     */
+    public static function getCartData() {
+
+        $cart_items = session('cart_items', []);
+
+        $cart_data = [];
+        $products = self::with('media')->whereIn('id', array_keys($cart_items))->get();
+
+        foreach ($products as $product) {
+            $cart_data[] = [
+                'id' => $product->id,
+                'name' => $product->name,
+                'category' => $product->type->name,
+                'quantity' => $cart_items[$product->id],
+                'image' => $product->image,
+            ];
+        }
+
+        return $cart_data;
+    }
+
 }
index ac248b32df5ab8e21e7dcfbacbfc68fa02317e4f..ea8aa7ba1055af153a86578ae9423e7fa6c9a143 100644 (file)
@@ -42,6 +42,11 @@ class Base extends TemplatePage
             'tab' => $tab,
         ]);
 
+        $this->addField(['name' => 'form_button_text',
+            'type' => 'Text',
+            'label' => 'Texte du bouton',
+            'tab' => $tab]);
+
         $this->addField(['name' => 'form_confirmation',
             'type' => 'Text',
             'label' => 'Message de confirmation',
diff --git a/public/images/tick.svg b/public/images/tick.svg
new file mode 100644 (file)
index 0000000..e76b080
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17.4 13"><path d="M5.9 13L0 7.1l1.4-1.4 4.5 4.4L16 0l1.4 1.4z"/></svg>
index 104b03a32dc2d4306eca4590583d28704182e715..b9759ab6ba3753de2944699530637bba3d7f42d5 100644 (file)
@@ -43,14 +43,22 @@ const app = new Vue({
     },
 
     mounted() {
-        eventBus.$on('update-item-quantity', update => {
-            const index = this.items.findIndex((item) => item.id == update.id);
-            this.items[index].quantity = update.quantity;
+
+        eventBus.$on('add-item', data => {
+            data.action = 'add';
+            this.saveCart(data);
+        });
+
+        eventBus.$on('update-item', data => {
+            data.action = 'update';
+            this.saveCart(data);
         });
 
         eventBus.$on('delete-item', id => {
-            const index = this.items.findIndex((item) => item.id == id);
-            this.items.splice(index, 1);
+            this.saveCart({
+                action: 'delete',
+                id: id
+            });
         });
     },
 
@@ -63,6 +71,20 @@ const app = new Vue({
 
     methods: {
 
+      saveCart(data) {
+          let root = this;
+
+          axios.post('/ajax/cart', data)
+              .then(function(response) {
+                  console.log('Cart updated');
+                  console.table(response.data);
+                  root.items = response.data;
+              })
+              .catch(function(error) {
+                  console.error('Error saving cart!', error);
+              });
+      },
+
       openCart() {
         document.body.classList.add('cart-open');
       },
index 81b6bf15237e341ec084ed344970b0a17dca309a..e60158278cafce5e90572fe20dd68eae255a0a75 100644 (file)
@@ -16,7 +16,7 @@
 
         props: {
             items: {
-                type: Object,
+                type: Array,
                 required: true,
             }
         },
diff --git a/resources/js/components/CartAdd.vue b/resources/js/components/CartAdd.vue
new file mode 100644 (file)
index 0000000..f0f4f2d
--- /dev/null
@@ -0,0 +1,46 @@
+<template>
+    <button class="btn" @click="addToCart" :class="{'btn-no-hover bg-navy' : isAdded }" :disabled="isAdded">
+        <span class="btn-text relative">
+            <span :class="{'opacity-0': isAdded}">
+                <slot></slot>
+            </span>
+            <span class="absolute top-0 left-0 w-full h-full flex items-center justify-center" v-if="isAdded">
+                <slot name="success-message"></slot>
+            </span>
+        </span>
+    </button>
+</template>
+
+<script>
+
+    export default {
+        name: "CartAdd",
+
+        props: {
+            productId: {
+                type: Number,
+                required: true,
+            }
+        },
+
+        data() {
+            return {
+                isAdded: false,
+            };
+        },
+
+        methods: {
+            addToCart() {
+                eventBus.$emit('add-item', {
+                    id: this.productId,
+                    quantity: 1,
+                });
+                this.isAdded = true;
+            }
+        }
+    }
+</script>
+
+<style scoped>
+
+</style>
index d9e347b0d7491c901a4191372f06e9d36bcc2074..09753f57bfa83bc1f7eb3a2fe2715cc2d31628b4 100644 (file)
@@ -38,7 +38,7 @@
 
         methods: {
             updateQuantity(newValue, oldValue) {
-                eventBus.$emit('update-item-quantity', {
+                eventBus.$emit('update-item', {
                     id: this.item.id,
                     quantity: newValue,
                 });
index 26ac84fc62395f04e53aa26f42cd90b0e758e5d1..59c976e0787036bcfb3cdbf32ca03f0d93cbbb24 100644 (file)
@@ -6,6 +6,9 @@
   &-text
     @apply z-10 relative
 
+  &-no-hover:before
+    display: none
+
   &:hover:before
     transform: scaleX(1)
     opacity: 1
index 5c8cae58d5bce0330dbea3a91231d47dd823a77a..3c1c6d28dfd9c5b678002ebe94957dbb9229b1d4 100644 (file)
@@ -9,6 +9,8 @@
 
 <div class="text-block {{ $class }} {{ $padding }}">
 
+    {{ $preTitle ?? '' }}
+
     @isset($title)
         <{{ $titleTag }} class="{{ $titleClass }}">{{ $title }}</{{ $titleTag }}>
     @endisset
index 1766fa30037b72cdf8583894a771f77bd77b56f4..668dd49ebcea191fc477eee1fb12b451cf213ec1 100644 (file)
 <body class="template-{{ $view_name }} {{ $body_class ?? '' }} font-body text-grey-dark">
 @include('cubist::body.begin')
 
-@php
-    if (config('features.quote')) {
-        //#### Generate temporary cart data
-        $cart_items = [];
-        for ($i = 0; $i < 6; $i++) {
-            $cart_items[$i] = [
-                'id' => $i + 1,
-                'quantity' => rand(1, 15),
-                'name' => 'Modèle '. rand(1000, 1500),
-                'category' => 'Capteur de force',
-                'image' => '/storage/products/'. rand(1,6) .'.png',
-            ];
-        }
-    } else {
-        $cart_items = [];
-    }
-@endphp
-
-<div id="app" class="flex flex-col min-h-screen" data-cart-items='@json($cart_items)'>
+<div id="app" class="flex flex-col min-h-screen" data-cart-items='@json(\App\Models\Product::getCartData())'>
 
     @include('partials.header')
 
index e17e2132f0d45bb095cef0671170d01f3ffcba15..949c494c888008cfd83a9bb9b9d227c96b292843 100644 (file)
@@ -10,7 +10,7 @@
 
             {{-- Nested divs to allow grey backgrounds of columns to match the height of their content instead of total height --}}
             <div v-if="cartItemCount > 0">
-                <cart :items='items' class="cart-page bg-grey-100 p-1v"></cart>
+                <cart :items='items' class="cart-page bg-grey-100 p-1v pb-0 overflow-hidden"></cart>
             </div>
 
             <div>
index e71d3cc24762dd08010b8ab1aac747ead00dcbf6..4addb9e6fee25cbc3f356b4cd4d1d1b73a7d46e6 100644 (file)
@@ -7,9 +7,9 @@
 @section('content')
 
     <content class="pt-1v">
-        <text-block title-class="h1 text-6xl" title-tag="h1">
-            <slot name="title">
-                {{$product->name}}
+        <text-block title-class="h1 text-6xl" title-tag="h1" :title="$product->name">
+            <slot name="preTitle">
+                <div class="text-navy text-bold font-display text-xl -mb-2">Ref: {{ $product->reference }}</div>
             </slot>
         </text-block>
 
 
 
                 @if(config('features.quote'))
-                    <link-button href="#" class="align-middle">{{__('Ajouter à ma sélection')}}</link-button>
+                    <cart-add :product-id="{{ $product->id }}" class="align-middle">
+                        {{__('Ajouter à ma sélection')}}
+
+                        <template v-slot:success-message>
+                           @svg('tick', 'w-4 mr-3') {{ __('Produit ajouté') }}
+                        </template>
+                    </cart-add>
 
                     <span class="font-display text-lg inline-block align-middle rounded-full border-grey-dark border-2 h-8 w-8 text-center ml-6">?</span>
                 @endif
                             {{-- Image holder --}}
                             <div class="product-img-holder">
                                 <div class="product-img"
-                                     style="background-image: url({{$rel->getImageUrl('images','',asset('images/product-details/product-placeholder.svg')) }})"></div>
+                                     style="background-image: url({{ $rel->getEntity()->image }})"></div>
                             </div>
 
                             {{-- Product details --}}
index e992ba0a7d42b612bf8e3e39c8c523e100fdb28b..c9de1e851e2e0f21e2106a8858728dfec0c3f38e 100644 (file)
                             <a href="{{ $product_URL }}">
                                 <div class="product-img-holder">
                                     <div class="product-img"
-                                         style="background-image: url({{$product->getImageUrl('images','',asset('images/product-details/product-placeholder.svg')) }})"></div>
+                                         style="background-image: url({{ $product->getEntity()->image }})"></div>
                                 </div>
                             </a>
 
index 1628bed1bb5fa3a8e1f480e7770c18fa215588dd..4fd98106c0389a60f4f6202c3eaf82b76f0ea522 100644 (file)
                 @endforeach
             </div>
 
-                <div class="form-endmessage mt-5 text-grey-dark">
+                <div class="form-endmessage my-6 text-grey-dark text-sm">
                     @markdown($global->get('form_privacy'))
                 </div>
                 <div class="flex justify-between items-center xs:flex-col-reverse">
                     <span class="text-grey-dark xs:self-start xs:mt-5">*{{__('Champs obligatoires')}}</span>
                     <button type="submit" class="btn btn-custom xs:w-full"
-                            data-sending="{{__('Envoi en cours')}}">{{__('Envoyer')}}</button>
+                            data-sending="{{__('Envoi en cours')}}">{{ $page->get('form_button_text', __('Envoyer')) }}</button>
                 </div>
             </form>
         </div>
index 0f907d062f03b77aa705a2e130533cb3eb7c0323..f6b9de7f5ac3d5b4b1806e11d4cb7e0957f9b8b4 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -4274,6 +4274,11 @@ lodash@^4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
   integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
 
+lodash@^4.17.15:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
 loglevel@^1.6.2:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.2.tgz#668c77948a03dbd22502a3513ace1f62a80cc372"