crm signle optin

This commit is contained in:
Olivier Legat 2021-04-06 14:08:57 +02:00
parent a2d6d0f6f1
commit 293cb74391
13 changed files with 650 additions and 14 deletions

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use App\Models\Content;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class HomeController extends Controller
{
public function index(Request $request)
{
$content = Content::where('key', 'accueil')->first();
return view('home', compact('content'));
}
/**
* Call the CRM to add a single optin
* @param String $email Email to add as a single optin
* @return String Message to print to the customer
*/
public function addOptIn(Request $request)
{
$validator = Validator::make($request->all(), ['email' => 'required|email']);
if ($validator->fails()) {
return __("L'adresse email est incorrecte");
}
$email = $request->email;
/** @var \App\Lib\LDCCRM\ApiV1 */
$crm = app('crmApi');
$result = $crm->createSingleOptin($email);
if ($result->status === "OK") {
return __("Votre inscription à la newsletter a été prise en compte");
} else {
switch ($result->code) {
case 120:
return __("L'adresse email est incorrecte");
case 121:
return __("Vous êtes déjà inscrit avec cette adresse email");
default:
Log::alert('Failed to add a single opt-in : ' . $result->message);
return __("Une erreur est survenue, merci de réessayer ultérieurement");
}
}
}
}

View file

@ -8,6 +8,9 @@ use Illuminate\Http\Request;
class IndexController extends Controller
{
/**
* @deprecated
*/
public function index(Request $request)
{
$content = Content::where('key', 'accueil')->first();

6
package-lock.json generated
View file

@ -8973,6 +8973,12 @@
"is-number": "^7.0.0"
}
},
"toastify-js": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.10.0.tgz",
"integrity": "sha512-xuO499S1sw9gTdiBkZSMNE4sIoiQv/y0y534F7g2dcprVMAJZIdtOaphqiQlNJPfh0YQFDvgD4R3KcX5r94cAQ==",
"dev": true
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",

View file

@ -20,6 +20,7 @@
"resolve-url-loader": "^3.1.0",
"sass-loader": "^8.0.2",
"swiper": "^6.3.4",
"toastify-js": "^1.10.0",
"vue-template-compiler": "^2.6.12"
},
"dependencies": {

File diff suppressed because one or more lines are too long

View file

@ -40886,6 +40886,428 @@ exports.clearImmediate = (typeof self !== "undefined" && self.clearImmediate) ||
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../webpack/buildin/global.js */ "./node_modules/webpack/buildin/global.js")))
/***/ }),
/***/ "./node_modules/toastify-js/src/toastify.js":
/*!**************************************************!*\
!*** ./node_modules/toastify-js/src/toastify.js ***!
\**************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
/*!
* Toastify js 1.10.0
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
(function(root, factory) {
if ( true && module.exports) {
module.exports = factory();
} else {
root.Toastify = factory();
}
})(this, function(global) {
// Object initialization
var Toastify = function(options) {
// Returning a new init object
return new Toastify.lib.init(options);
},
// Library version
version = "1.10.0";
// Defining the prototype of the object
Toastify.lib = Toastify.prototype = {
toastify: version,
constructor: Toastify,
// Initializing the object with required parameters
init: function(options) {
// Verifying and validating the input object
if (!options) {
options = {};
}
// Creating the options object
this.options = {};
this.toastElement = null;
// Validating the options
this.options.text = options.text || "Hi there!"; // Display message
this.options.node = options.node // Display content as node
this.options.duration = options.duration === 0 ? 0 : options.duration || 3000; // Display duration
this.options.selector = options.selector; // Parent selector
this.options.callback = options.callback || function() {}; // Callback after display
this.options.destination = options.destination; // On-click destination
this.options.newWindow = options.newWindow || false; // Open destination in new window
this.options.close = options.close || false; // Show toast close icon
this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : "toastify-top"; // toast position - top or bottom
this.options.positionLeft = options.positionLeft || false; // toast position - left or right
this.options.position = options.position || ''; // toast position - left or right
this.options.backgroundColor = options.backgroundColor; // toast background color
this.options.avatar = options.avatar || ""; // img element src - url or a path
this.options.className = options.className || ""; // additional class names for the toast
this.options.stopOnFocus = options.stopOnFocus === undefined? true: options.stopOnFocus; // stop timeout on focus
this.options.onClick = options.onClick; // Callback after click
this.options.offset = options.offset || { x: 0, y: 0 }; // toast offset
this.options.escapeMarkup = options.escapeMarkup !== undefined ? options.escapeMarkup : true;
this.options.style = options.style || {};
this.options.style.background = this.options.style.background || options.backgroundColor;
// Returning the current object for chaining functions
return this;
},
// Building the DOM element
buildToast: function() {
// Validating if the options are defined
if (!this.options) {
throw "Toastify is not initialized";
}
// Creating the DOM object
var divElement = document.createElement("div");
divElement.className = "toastify on " + this.options.className;
// Positioning toast to left or right or center
if (!!this.options.position) {
divElement.className += " toastify-" + this.options.position;
} else {
// To be depreciated in further versions
if (this.options.positionLeft === true) {
divElement.className += " toastify-left";
console.warn('Property `positionLeft` will be depreciated in further versions. Please use `position` instead.')
} else {
// Default position
divElement.className += " toastify-right";
}
}
// Assigning gravity of element
divElement.className += " " + this.options.gravity;
if (this.options.backgroundColor) {
// This is being deprecated in favor of using the style HTML DOM property
console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.');
}
// Loop through our style object and apply styles to divElement
for (const property in this.options.style) {
divElement.style[property] = this.options.style[property];
}
// Adding the toast message/node
if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
// If we have a valid node, we insert it
divElement.appendChild(this.options.node)
} else {
if (this.options.escapeMarkup) {
divElement.innerText = this.options.text;
} else {
divElement.innerHTML = this.options.text;
}
if (this.options.avatar !== "") {
var avatarElement = document.createElement("img");
avatarElement.src = this.options.avatar;
avatarElement.className = "toastify-avatar";
if (this.options.position == "left" || this.options.positionLeft === true) {
// Adding close icon on the left of content
divElement.appendChild(avatarElement);
} else {
// Adding close icon on the right of content
divElement.insertAdjacentElement("afterbegin", avatarElement);
}
}
}
// Adding a close icon to the toast
if (this.options.close === true) {
// Create a span for close element
var closeElement = document.createElement("span");
closeElement.innerHTML = "&#10006;";
closeElement.className = "toast-close";
// Triggering the removal of toast from DOM on close click
closeElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.removeElement(this.toastElement);
window.clearTimeout(this.toastElement.timeOutValue);
}.bind(this)
);
//Calculating screen width
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Adding the close icon to the toast element
// Display on the right if screen width is less than or equal to 360px
if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) {
// Adding close icon on the left of content
divElement.insertAdjacentElement("afterbegin", closeElement);
} else {
// Adding close icon on the right of content
divElement.appendChild(closeElement);
}
}
// Clear timeout while toast is focused
if (this.options.stopOnFocus && this.options.duration > 0) {
var self = this;
// stop countdown
divElement.addEventListener(
"mouseover",
function(event) {
window.clearTimeout(divElement.timeOutValue);
}
)
// add back the timeout
divElement.addEventListener(
"mouseleave",
function() {
divElement.timeOutValue = window.setTimeout(
function() {
// Remove the toast from DOM
self.removeElement(divElement);
},
self.options.duration
)
}
)
}
// Adding an on-click destination path
if (typeof this.options.destination !== "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
if (this.options.newWindow === true) {
window.open(this.options.destination, "_blank");
} else {
window.location = this.options.destination;
}
}.bind(this)
);
}
if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.options.onClick();
}.bind(this)
);
}
// Adding offset
if(typeof this.options.offset === "object") {
var x = getAxisOffsetAValue("x", this.options);
var y = getAxisOffsetAValue("y", this.options);
var xOffset = this.options.position == "left" ? x : "-" + x;
var yOffset = this.options.gravity == "toastify-top" ? y : "-" + y;
divElement.style.transform = "translate(" + xOffset + "," + yOffset + ")";
}
// Returning the generated element
return divElement;
},
// Displaying the toast
showToast: function() {
// Creating the DOM object for the toast
this.toastElement = this.buildToast();
// Getting the root element to with the toast needs to be added
var rootElement;
if (typeof this.options.selector === "string") {
rootElement = document.getElementById(this.options.selector);
} else if (this.options.selector instanceof HTMLElement || this.options.selector instanceof ShadowRoot) {
rootElement = this.options.selector;
} else {
rootElement = document.body;
}
// Validating if root element is present in DOM
if (!rootElement) {
throw "Root element is not defined";
}
// Adding the DOM element
rootElement.insertBefore(this.toastElement, rootElement.firstChild);
// Repositioning the toasts in case multiple toasts are present
Toastify.reposition();
if (this.options.duration > 0) {
this.toastElement.timeOutValue = window.setTimeout(
function() {
// Remove the toast from DOM
this.removeElement(this.toastElement);
}.bind(this),
this.options.duration
); // Binding `this` for function invocation
}
// Supporting function chaining
return this;
},
hideToast: function() {
if (this.toastElement.timeOutValue) {
clearTimeout(this.toastElement.timeOutValue);
}
this.removeElement(this.toastElement);
},
// Removing the element from the DOM
removeElement: function(toastElement) {
// Hiding the element
// toastElement.classList.remove("on");
toastElement.className = toastElement.className.replace(" on", "");
// Removing the element from DOM after transition end
window.setTimeout(
function() {
// remove options node if any
if (this.options.node && this.options.node.parentNode) {
this.options.node.parentNode.removeChild(this.options.node);
}
// Remove the elemenf from the DOM, only when the parent node was not removed before.
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
// Calling the callback function
this.options.callback.call(toastElement);
// Repositioning the toasts again
Toastify.reposition();
}.bind(this),
400
); // Binding `this` for function invocation
},
};
// Positioning the toasts on the DOM
Toastify.reposition = function() {
// Top margins with gravity
var topLeftOffsetSize = {
top: 15,
bottom: 15,
};
var topRightOffsetSize = {
top: 15,
bottom: 15,
};
var offsetSize = {
top: 15,
bottom: 15,
};
// Get all toast messages on the DOM
var allToasts = document.getElementsByClassName("toastify");
var classUsed;
// Modifying the position of each toast element
for (var i = 0; i < allToasts.length; i++) {
// Getting the applied gravity
if (containsClass(allToasts[i], "toastify-top") === true) {
classUsed = "toastify-top";
} else {
classUsed = "toastify-bottom";
}
var height = allToasts[i].offsetHeight;
classUsed = classUsed.substr(9, classUsed.length-1)
// Spacing between toasts
var offset = 15;
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
// Show toast in center if screen with less than or qual to 360px
if (width <= 360) {
// Setting the position
allToasts[i].style[classUsed] = offsetSize[classUsed] + "px";
offsetSize[classUsed] += height + offset;
} else {
if (containsClass(allToasts[i], "toastify-left") === true) {
// Setting the position
allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px";
topLeftOffsetSize[classUsed] += height + offset;
} else {
// Setting the position
allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px";
topRightOffsetSize[classUsed] += height + offset;
}
}
}
// Supporting function chaining
return this;
};
// Helper function to get offset.
function getAxisOffsetAValue(axis, options) {
if(options.offset[axis]) {
if(isNaN(options.offset[axis])) {
return options.offset[axis];
}
else {
return options.offset[axis] + 'px';
}
}
return '0px';
}
function containsClass(elem, yourClass) {
if (!elem || typeof yourClass !== "string") {
return false;
} else if (
elem.className &&
elem.className
.trim()
.split(/\s+/gi)
.indexOf(yourClass) > -1
) {
return true;
} else {
return false;
}
}
// Setting up the prototype for the init object
Toastify.lib.init.prototype = Toastify.lib;
// Returning the Toastify function to be assigned to the window object/module
return Toastify;
});
/***/ }),
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./resources/views/front/canard/js/components/BasketPanel.vue?vue&type=template&id=7a3333de&":
@ -55884,13 +56306,17 @@ module.exports = function(module) {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! swiper/swiper-bundle */ "./node_modules/swiper/swiper-bundle.js");
/* harmony import */ var swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(swiper_swiper_bundle__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ "./node_modules/vuex/dist/vuex.esm.js");
/* harmony import */ var toastify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! toastify-js */ "./node_modules/toastify-js/src/toastify.js");
/* harmony import */ var toastify_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(toastify_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vuex */ "./node_modules/vuex/dist/vuex.esm.js");
__webpack_require__(/*! ./bootstrap */ "./resources/views/front/canard/js/bootstrap.js");
__webpack_require__(/*! ./cart.js */ "./resources/views/front/canard/js/cart.js");
window.Toastify = toastify_js__WEBPACK_IMPORTED_MODULE_1___default.a;
window.Vue = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.common.js"); // Vuex
@ -55912,7 +56338,7 @@ Vue.component('basket-counter', {
},
template: '<span class="header-actions__item-icon icon icon_cart"><span class="icon__count">{{ count }}</span></span>'
});
var store = new vuex__WEBPACK_IMPORTED_MODULE_1__["default"].Store({
var store = new vuex__WEBPACK_IMPORTED_MODULE_2__["default"].Store({
state: {
panier: {},
cartTotals: {}

View file

@ -31,7 +31,8 @@
@include('partials.ranges-slider')
@yield('products-selection', view('partials.products-selection'))
@yield('engagements', view('partials.engagements'))
@yield('subscribe', view('partials.subscribe'))
@include('partials.subscribe')
@yield('subscribe')
{{--
<h1>Home</h1>

View file

@ -1,7 +1,9 @@
import Swiper from 'swiper/swiper-bundle';
import Toastify from "toastify-js";
require('./bootstrap');
require('./cart.js');
window.Toastify = Toastify;
window.Vue = require('vue');
// Vuex

View file

@ -42,7 +42,7 @@
</div><!-- page-wrap END -->
<script src="{{ asset(config('app.theme').'/js/app.js') }}"></script>
@yield('scripts')
@stack('scripts')
</body>
</html>

View file

@ -1,11 +1,53 @@
@section('subscribe')
<div class="subscribe section">
<div class="subscribe__wrapper wrapper">
<div class="subscribe__block">
<div class="subscribe__title title title_medium">Recevez nos promotions et nouveautés</div>
<form class="subscribe__form form">
<input class="form__input" type="email" placeholder="Renseignez votre adresse électronique" />
<button class="btn btn_blue">S’inscrire</button>
</form>
{{--<form class="subscribe__form form">
@csrf--}}
<div class="subscribe__form form">
<input class="form__input" type="email" id="mailNewsletter" placeholder="Renseignez votre adresse électronique" />
<button class="btn btn_blue" id="buttonNewsletter">S’inscrire</button>
</div>
{{--</form>--}}
</div>
</div>
</div>
@stop
@push('scripts')
<script>
let toast = {
duration: 10000,
newWindow: false,
close: true,
className: "notifications",
gravity: "top",
offset: {
x: "2em",
y: "3em"
},
stopOnFocus: true // Prevents dismissing of toast on hover
};
document.getElementById("buttonNewsletter").onclick = function(event) {
axios
.post('{{ route('add-single-optin') }}', {
email: document.getElementById("mailNewsletter").value
})
.then(function(response) {
toast.text = response.data;
toast.destination = undefined;
Toastify(toast).showToast();
document.getElementById("mailNewsletter").value = "";
})
.catch(function(error) {
console.log(error);
toast.text =
"Une erreur est survenue, merci de réessayer ultérieurement";
toast.destination = undefined;
Toastify(toast).showToast();
});
};
</script>
@endpush

View file

@ -0,0 +1,12 @@
.notifications {
display: flex;
padding: 1rem 1rem;
font-size: 2rem;
//font-family: "CreamyScriptRegular";
line-height: 2rem;
background: linear-gradient(135deg, $blue, $green);
.toast-close {
padding: 0 1rem;
}
}

View file

@ -2,8 +2,10 @@
@import 'reset';
@import '../../../../../node_modules/swiper/swiper-bundle.min.css';
@import "~toastify-js/src/toastify.css";
@import 'variables';
@import 'fonts';
@import 'notifications';
@import 'main';
@import 'cart';
@import 'tunnel';

View file

@ -3,6 +3,7 @@
use Illuminate\Support\Facades\Route;
// FRONT
use App\Http\Controllers\IndexController;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\BasketController;
use App\Http\Controllers\ProductsController;
use App\Http\Controllers\CustomerController;
@ -12,11 +13,9 @@ use App\Http\Controllers\Bo\DashboardController;
use App\Http\Controllers\Bo\ContentController;
use App\Http\Controllers\Bo\FaqController;
use App\Http\Controllers\Bo\SettingsController;
use App\Http\Controllers\Bo\OrdersController;
// AUTH
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Auth\ResetPasswordController;
use App\Http\Controllers\Bo\OrdersController;
/*
|--------------------------------------------------------------------------
@ -29,7 +28,7 @@ use App\Http\Controllers\Bo\OrdersController;
|
*/
Route::get('/', [IndexController::class, 'index'])->name('home');
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/mentions-legales', [IndexController::class, 'legalNotice'])->name('legal-notice');
Route::get('/cgv', [IndexController::class, 'terms'])->name('terms');
Route::get('/politique-de-confidentialite', [IndexController::class, 'privacy'])->name('privacy');
@ -40,8 +39,10 @@ Route::get('/gamme-de-produits/{rangeSlug}', [ProductsController::class, 'range'
Route::get('/produits', [ProductsController::class, 'products'])->name('products');
Route::get('/produit/{productSlug}', [ProductsController::class, 'product'])->name('product')->middleware('redirect-slugs');
// Route ajax pour Single Optin
Route::post('/optin', [HomeController::class, 'addOptIn'])->name('add-single-optin');
//Contact
//Route::get('/contact', [IndexController::class, 'contact'])->name('contact');
Route::get('/contactez-nous', [ContactController::class, 'contact'])->name('contact');
Route::post('/contactez-nous', [ContactController::class, 'contactSubmit'])->name('contact-us.submit');