]> _ Git - ccv-wordpress.git/commitdiff
Form processing, refactoring and bug fixes. WIP #3445 @9
authorStephen Cameron <stephen@cubedesigners.com>
Thu, 30 Apr 2020 19:53:51 +0000 (21:53 +0200)
committerStephen Cameron <stephen@cubedesigners.com>
Thu, 30 Apr 2020 19:53:51 +0000 (21:53 +0200)
16 files changed:
wp-content/mu-plugins/cube/src/Elementor/Widgets/Form.php
wp-content/mu-plugins/cube/src/Forms/Base.php
wp-content/mu-plugins/cube/src/Forms/Contact.php [new file with mode: 0644]
wp-content/themes/CCV/index.php
wp-content/themes/CCV/resources/assets/scripts/forms.js [deleted file]
wp-content/themes/CCV/resources/assets/scripts/forms/form-loading-indicator.js [new file with mode: 0644]
wp-content/themes/CCV/resources/assets/scripts/forms/forms.js [new file with mode: 0644]
wp-content/themes/CCV/resources/assets/scripts/forms/parsley-setup.js [new file with mode: 0644]
wp-content/themes/CCV/resources/assets/styles/components/forms.styl
wp-content/themes/CCV/resources/views/forms/common/wrapper.blade.php
wp-content/themes/CCV/resources/views/forms/consultation.blade.php
wp-content/themes/CCV/resources/views/forms/contact.blade.php [new file with mode: 0644]
wp-content/themes/CCV/resources/views/forms/training.blade.php
wp-content/themes/CCV/resources/views/layouts/app.blade.php
wp-content/themes/CCV/resources/views/partials/footer.blade.php
wp-content/themes/CCV/webpack.mix.js

index a712206ec77fe872618d7b3aa01aad61587551ac..ab75ec11bf111754f6693e1737d4f745327ebfdd 100644 (file)
@@ -78,6 +78,19 @@ class Form extends _Base {
                 'placeholder' => $form_base->get_destination(),
             ]
         );
+
+        $this->add_control(
+            'success_message',
+            [
+                'type' => Controls_Manager::WYSIWYG,
+                'label' => __('Message après la soumission réussie', 'cube'),
+                'label_block' => true,
+                'default' => __('Nous vous remercions pour votre demande.', 'cube'),
+                'dynamic' => [
+                    'active' => false,
+                ],
+            ]
+        );
         
         $this->end_controls_section();
 
@@ -94,10 +107,10 @@ class Form extends _Base {
 
         $form_name = $this->get_settings('form_name');
         $destination = $this->get_settings('destination');
+        $success_message = $this->get_settings('success_message');
 
         // Serialize and encode configuration for use in hidden field
-        // Currently only the form name and destination address is set but this might be extended in the future
-        $config = base64_encode(serialize(compact('form_name', 'destination')));
+        $config = base64_encode(serialize(compact('form_name', 'destination', 'success_message')));
 
         $base_form = new BaseForm();
         $forms = $base_form->get_forms();
index f81b0697267817c18b7cef6ca683eb13d571a6e0..694fbd773f1a23abfb982b5c509a095844fe9f8d 100644 (file)
@@ -13,9 +13,11 @@ class Base
     private $forms = [ // All available forms
         'consultation' => Consultation::class,
         'training' => Training::class,
+        'contact' => Contact::class,
     ];
 
     private $fields = [];
+    private $config;
     private $data;
 
     public $form_title = '';
@@ -52,7 +54,7 @@ class Base
 
         //=== Cube Forms
         // Base forms functionality - enqueued directly since it is always required
-        wp_enqueue_script('cube-forms', asset('scripts/forms.js'), ['jquery'], null, true);
+        wp_enqueue_script('cube-forms', asset('scripts/forms/forms.js'), ['jquery'], null, true);
 
         // JS variables
         wp_add_inline_script(
@@ -67,7 +69,8 @@ class Base
 
 
         //=== Parsley JS Validation
-        wp_enqueue_script('parsleyjs', asset('scripts/parsley/parsley.min.js'), ['jquery'], null, true);
+        $parsley_path = 'scripts/forms/parsley'; // Relative to dist folder
+        wp_enqueue_script('parsleyjs', asset("$parsley_path/parsley.min.js"), ['jquery'], null, true);
 
         // Load localisation if available (English is default so not needed)
         $parsley_locales = ['ar', 'fr', 'ru']; // List of possible locales (see webpack.mix.js for which ones are copied)
@@ -76,33 +79,12 @@ class Base
 
             if (in_array($current_locale, $parsley_locales)) {
                 // Include it inline since it's small and to save an extra HTTP request
-                wp_add_inline_script('parsleyjs', asset("scripts/parsley/locale/{$current_locale}.js")->contents());
+                wp_add_inline_script('parsleyjs', asset("$parsley_path/locale/$current_locale.js")->contents());
             }
         }
 
         // Parsley JS initialisation and overrides
-        // Ref: https://stackoverflow.com/a/30122442
-        wp_add_inline_script('parsleyjs', "
-            const parsleyConfig = {
-                classHandler: function(parsleyField) {
-                    var fieldWrapper = parsleyField.\$element.parents('.form-field');
-                    return (fieldWrapper.length > 0) ? fieldWrapper : parsleyField;
-                },
-            
-                errorsContainer: function(parsleyField) {
-                    var inputWrapper = parsleyField.\$element.parents('.form-field-input');
-                    return (inputWrapper.length > 0) ? inputWrapper : parsleyField;
-                }
-            };
-            
-            jQuery('.cube-form').parsley(parsleyConfig);
-            
-            // On validation errors, scroll to the first invalid input
-            jQuery.listen('parsley:field:error', function() {
-                window.scrollTo({...jQuery('.parsley-error').first().offset(), ...{behavior: 'smooth'}});
-            });
-            
-        ");
+        wp_add_inline_script('parsleyjs', asset("$parsley_path/parsley-setup.js")->contents());
 
 
         //=== Flatpickr Calendar
@@ -159,25 +141,38 @@ class Base
 
         // First, check that request is valid via nonce value
         if (check_ajax_referer($this->action_name, 'nonce', false) === false) {
-            wp_send_json_error(new \WP_Error('nonce', 'Failed security validation'));
+            wp_send_json([
+                'message'  => [
+                    'type' => 'error',
+                    'text' => __('Une erreur est survenue. Veuillez réessayer.', 'ccv'),
+                ],
+                'debug' => 'Failed security validation (nonce)',
+            ]);
         }
 
         // Next, get the form configuration and decode + unserialize it
         $config = unserialize(base64_decode($_POST['config']));
 
-        //check which form is being sent and if it is valid
+        // Check which form is being sent and if it is valid
         $form_name = $config['form_name'] ?? null;
 
         if (!($form_name && array_key_exists($form_name, $this->forms))) {
-            wp_send_json_error(new \WP_Error('invalid_form', "Unknown form ($form_name)"));
+            wp_send_json([
+                'message'  => [
+                    'type' => 'error',
+                    'text' => __('Une erreur est survenue. Veuillez réessayer.', 'ccv'),
+                ],
+                'debug' => "Unknown form ($form_name)",
+            ]);
         }
 
         // Load the form and process it...
         /* @var $form Base */
         $form = new $this->forms[$form_name];
         $form->register_fields();
-        $form->set_destination($config['destination']);
         $form->data = $this->get_form_data();
+        $form->config = $config;
+        $form->set_destination($config['destination']);
         $form->process();
         exit;
     }
@@ -192,7 +187,7 @@ class Base
         $data = [];
 
         $to           = $this->destination;
-        $from         = 'CCV <no-reply@ccv-montpellier.fr';
+        $from         = 'CCV <no-reply@ccv-montpellier.fr>';
         $subject      = $this->form_title;
         $content_type = 'text/html';
         $charset      = get_bloginfo('charset');
@@ -212,9 +207,20 @@ class Base
         $success = wp_mail($to, $subject, $message, $headers);
 
         if ($success) {
-            wp_send_json_success(__('Success !', 'ccv'));
+            wp_send_json([
+                'message'  => [
+                    'type' => 'success',
+                    'text' => $this->config['success_message'],
+                ],
+                'hide_form' => true,
+            ]);
         } else {
-            wp_send_json_error();
+            wp_send_json([
+                'message'  => [
+                    'type' => 'error',
+                    'text' => __("Erreur d'envoi du message. Veuillez réessayer.", "ccv"),
+                ]
+            ]);
         }
 
         $this->post_process();
@@ -470,7 +476,7 @@ class Base
 
     /**
      * HTML (submit) button
-     * @param $text Button label
+     * @param string $text Button label
      * @param array $settings
      * @return string
      */
@@ -478,11 +484,12 @@ class Base
         $default_settings = [
             'type' => 'submit',
             'class' => 'btn',
+            'loading_text' => '',
         ];
 
         $settings = array_merge($default_settings, $settings);
 
-        return '<button type="'. $settings['type'] .'" class="'. $settings['class'] .'">'. $text .'</button>';
+        return '<button type="'. $settings['type'] .'" class="'. $settings['class'] .'" data-loading-text="'. $settings['loading_text'] .'">'. $text .'</button>';
     }
 
 
diff --git a/wp-content/mu-plugins/cube/src/Forms/Contact.php b/wp-content/mu-plugins/cube/src/Forms/Contact.php
new file mode 100644 (file)
index 0000000..9ba13ac
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+namespace Cube\Forms;
+
+class Contact extends Base
+{
+    public function __construct() {
+        $this->form_title = __('Formulaire de contact', 'ccv');
+    }
+
+    function register_fields() {
+
+        parent::register_fields();
+
+        $this->add_field('last-name', _x('Nom', 'Nom de famille', 'ccv'), self::TEXT);
+        $this->add_field('first-name', __('Prénom', 'ccv'), self::TEXT);
+        $this->add_field('phone', __('Téléphone', 'ccv'), self::TEXT);
+    }
+}
index bf7ac5a18c787c93609c89816e598edeb230e749..3ca57daa96afd00b9fe346ab101fb78bb976b22c 100644 (file)
@@ -14,7 +14,7 @@
 <?php wp_body_open(); ?>
 <?php do_action('get_header'); ?>
 
-<div id="app">
+<div id="app" class="flex flex-col min-h-screen">
     <?php echo \Roots\view(\Roots\app('sage.view'), \Roots\app('sage.data'))->render(); ?>
 </div>
 
diff --git a/wp-content/themes/CCV/resources/assets/scripts/forms.js b/wp-content/themes/CCV/resources/assets/scripts/forms.js
deleted file mode 100644 (file)
index 7b2bb41..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-//=== Cube Forms
-// Inspired by HTMLForms: https://github.com/ibericode/html-forms/blob/master/assets/browserify/public.js
-
-const config = window.cube_forms_config || {};
-
-function handleSubmitEvents (e) {
-  const formEl = e.target;
-  if (formEl.className.indexOf('cube-form') < 0) {
-    return
-  }
-  e.preventDefault(); // always prevent default because we only want to send via AJAX
-  submitForm(formEl)
-}
-
-function submitForm(form) {
-  const formData = new FormData(form);
-
-  formData.append('action', config.action);
-  formData.append('nonce', config.nonce);
-
-  let request = new XMLHttpRequest();
-  //request.onreadystatechange = createRequestHandler(form);
-  request.open('POST', config.ajax_url, true);
-  request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
-  request.send(formData);
-  request = null;
-}
-
-document.addEventListener('submit', handleSubmitEvents, false); // useCapture=false to ensure we bubble upwards (and thus can cancel propagation)
diff --git a/wp-content/themes/CCV/resources/assets/scripts/forms/form-loading-indicator.js b/wp-content/themes/CCV/resources/assets/scripts/forms/form-loading-indicator.js
new file mode 100644 (file)
index 0000000..8fc6a8b
--- /dev/null
@@ -0,0 +1,66 @@
+'use strict';
+
+function getButtonText (button) {
+  return button.innerHTML ? button.innerHTML : button.value;
+}
+
+function setButtonText (button, text) {
+  button.innerHTML ? button.innerHTML = text : button.value = text;
+}
+
+function Loader (formElement) {
+  this.form = formElement;
+  this.button = formElement.querySelector('input[type="submit"], button[type="submit"]');
+  this.loadingInterval = 0;
+  this.character = '\u00B7'; // · middle dot character
+
+  if (this.button) {
+    this.originalButton = this.button.cloneNode(true);
+  }
+}
+
+Loader.prototype.setCharacter = function (c) {
+  this.character = c;
+};
+
+Loader.prototype.start = function () {
+  if (this.button) {
+    this.button.disabled = true; // Avoid multiple submits
+
+    // loading text
+    const loadingText = this.button.getAttribute('data-loading-text');
+    if (loadingText) {
+      setButtonText(this.button, loadingText);
+      return;
+    }
+
+    // Show AJAX loader
+    const styles = window.getComputedStyle(this.button);
+    this.button.style.width = styles.width;
+    setButtonText(this.button, this.character);
+    this.loadingInterval = window.setInterval(this.tick.bind(this), 500);
+  } else {
+    this.form.style.opacity = '0.5';
+  }
+};
+
+Loader.prototype.tick = function () {
+  // count chars, start over at 5 (3 dots + 2 spaces)
+  const text = getButtonText(this.button);
+  const loadingChar = this.character;
+  setButtonText(this.button, text.length >= 5 ? loadingChar : text + ' ' + loadingChar);
+};
+
+Loader.prototype.stop = function () {
+  if (this.button) {
+    this.button.disabled = false;
+    this.button.style.width = this.originalButton.style.width;
+    const text = getButtonText(this.originalButton);
+    setButtonText(this.button, text);
+    window.clearInterval(this.loadingInterval);
+  } else {
+    this.form.style.opacity = '';
+  }
+};
+
+module.exports = Loader;
diff --git a/wp-content/themes/CCV/resources/assets/scripts/forms/forms.js b/wp-content/themes/CCV/resources/assets/scripts/forms/forms.js
new file mode 100644 (file)
index 0000000..dcb7a0f
--- /dev/null
@@ -0,0 +1,111 @@
+//=== Cube Forms
+// Inspired by HTMLForms: https://github.com/ibericode/html-forms/blob/master/assets/browserify/public.js
+
+import Loader from './form-loading-indicator';
+
+const config = window.cube_forms_config || {};
+
+function clearFormMessages (formEl) {
+  const messageElements = formEl.querySelectorAll('.cube-form-message');
+  [].forEach.call(messageElements, (el) => {
+    el.parentNode.removeChild(el);
+  });
+}
+
+function addFormMessage (formEl, message, clear) {
+  const msgElement = document.createElement('div');
+  msgElement.className = 'cube-form-message cube-form-message-' + message.type;
+  msgElement.innerHTML = message.text; // uses innerHTML because we allow some HTML strings in the message settings
+  msgElement.role = 'alert';
+
+  let wrapperElement = formEl.querySelector('.cube-form-messages');
+
+  // If messages wrapper is missing, add it
+  if (!wrapperElement) {
+    wrapperElement = document.createElement('div');
+    wrapperElement.className = 'cube-form-messages';
+    formEl.appendChild(wrapperElement);
+    console.log('wrapper was missing...', wrapperElement);
+  }
+
+  if (clear) {
+    wrapperElement.innerHTML = '';
+  }
+
+  wrapperElement.appendChild(msgElement);
+  wrapperElement.classList.remove('hidden'); // Unhide messages
+}
+
+function handleSubmitEvents (e) {
+  const formEl = e.target;
+  if (formEl.className.indexOf('cube-form') < 0) {
+    return
+  }
+  e.preventDefault(); // always prevent default because we only want to send via AJAX
+  submitForm(formEl)
+}
+
+function submitForm(form) {
+  const formData = new FormData(form);
+
+  formData.append('action', config.action);
+  formData.append('nonce', config.nonce);
+
+  let request = new XMLHttpRequest();
+  request.onreadystatechange = createRequestHandler(form);
+  request.open('POST', config.ajax_url, true);
+  request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+  request.send(formData);
+  request = null;
+}
+
+function createRequestHandler (formEl) {
+  const loader = new Loader(formEl);
+  loader.start();
+
+  return function () {
+    // Is the XHR request complete? https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
+    if (this.readyState === 4) {
+      let response;
+      loader.stop();
+
+      if (this.status >= 200 && this.status < 400) {
+
+        try {
+          response = JSON.parse(this.responseText);
+        } catch (error) {
+          console.log('Cube Forms: failed to parse AJAX response.\n\nError: "' + error + '"');
+          return;
+        }
+
+        // Show form message
+        if (response.message) {
+          let clearMessages = response.message.type === 'error';
+          addFormMessage(formEl, response.message, clearMessages);
+        }
+
+        // Should we hide form?
+        if (response.hide_form) {
+          formEl.querySelector('.cube-form-content').style.display = 'none'
+        }
+
+        // Should we redirect?
+        if (response.redirect_url) {
+          window.location = response.redirect_url
+        }
+
+        // Clear form
+        // if (!response.error) {
+        //   formEl.reset()
+        // }
+      } else {
+        // Server error :(
+        console.log(this.responseText)
+      }
+    }
+  }
+}
+
+
+
+document.addEventListener('submit', handleSubmitEvents, false); // useCapture=false to ensure we bubble upwards (and thus can cancel propagation)
diff --git a/wp-content/themes/CCV/resources/assets/scripts/forms/parsley-setup.js b/wp-content/themes/CCV/resources/assets/scripts/forms/parsley-setup.js
new file mode 100644 (file)
index 0000000..7ecda22
--- /dev/null
@@ -0,0 +1,28 @@
+// ParsleyJS Setup - to be included after main library that will be copied from node_modules
+// Ref: https://stackoverflow.com/a/30122442
+(function($) {
+
+  const parsleyConfig = {
+    classHandler: function(parsleyField) {
+      let fieldWrapper = parsleyField.$element.parents('.form-field');
+      return (fieldWrapper.length > 0) ? fieldWrapper : parsleyField;
+    },
+
+    errorsContainer: function(parsleyField) {
+      let inputWrapper = parsleyField.$element.parents('.form-field-input');
+      return (inputWrapper.length > 0) ? inputWrapper : parsleyField;
+    }
+  };
+
+  $('.cube-form').parsley(parsleyConfig);
+
+  // On validation errors, scroll to the first invalid input
+  $.listen('parsley:field:error', function() {
+
+    let firstErrorOffset = $('.parsley-error').first().offset();
+    firstErrorOffset.top = Math.max(0, firstErrorOffset.top - 50); // Scroll a bit above the element
+
+    window.scrollTo({...firstErrorOffset, ...{behavior: 'smooth'}});
+  });
+
+})(jQuery);
index 7b0bdec4575cc4d5d14016b1a484173ac821ede8..f87cdc5fedf613edb4b956ded66381787bb1296e 100644 (file)
@@ -108,8 +108,8 @@ input[type="submit"]
   margin-bottom: 0.75em
 
 .form-field-input
-  > *:not(:first-child)
-    margin-left: 1.5em
+  > *:not(:last-child)
+    margin-right: 1.5em
 
   label
     white-space: nowrap // Ensure text stays beside input control
@@ -145,16 +145,10 @@ input[type="submit"]
       flex-basis: 100%
 
 
-// HTML Forms Plugin styling
-.hf-message
-  margin: 2em 0
-  border: 2px solid
-  padding: 1em
-  background-color: #ccc
-
-  &-success
-    background-color: #e8f5e9
-    color: #4caf50
+// Cube Forms
+.cube-form-message
+  &-error
+    @apply text-red
 
 // ParsleyJS validation
 .parsley-errors-list
@@ -164,7 +158,6 @@ input[type="submit"]
   left: 0
   bottom: -2em
   padding: 0
-  margin-left: 0 !important
   font-size: 0.7em
   white-space: nowrap
 
index 3f464566debc0623f28b954f9e1b9d8cc6112918..eecb3d4a09caa916ac46f66070a693b7782587f0 100644 (file)
@@ -9,6 +9,11 @@
 
   <input type="hidden" name="config" value="{{ $config }}">
 
-  @includeIf("forms/$form_name")
+  <div class="cube-form-content">
+    @includeIf("forms/$form_name")
+  </div>
+
+  {{-- Placeholder for form messages / response --}}
+  <div class="cube-form-messages hidden @stack('message_class')"></div>
 
 </form>
index b44d8c39bfd43dd3e597c5cac5d4225b7e8c87b4..300b0c55b091fbecc5672f5fda8be90c32ffe626 100644 (file)
       ]) !!}
       {!! $form->field('message', ['title_class' => 'font-light']) !!}
 
-      <div class="mt-4">
+      <div class="mt-3">
         <div class="custom-checkbox mb-1">
           <label>
             <input type="checkbox" name="send-to-team" value="{{ __('Oui') }}" checked class="ml-2">
 
   </div>
 
-  {!! $form->button(__('Envoyer votre demande', 'ccv'), ['class' => 'btn block mt-1v ml-auto']) !!}
+  {!!
+    $form->button(__('Envoyer votre demande', 'ccv'), [
+      'class' => 'btn block mt-1v ml-auto',
+      'loading_text' => __('Envoi en cours...', 'ccv'),
+    ])
+  !!}
 
 </div>
+
+{{-- Custom classes for form message container --}}
+@push('message_class')
+  py-2v pl-4v pr-3v xs:px-2v
+@endpush
diff --git a/wp-content/themes/CCV/resources/views/forms/contact.blade.php b/wp-content/themes/CCV/resources/views/forms/contact.blade.php
new file mode 100644 (file)
index 0000000..4b94f05
--- /dev/null
@@ -0,0 +1,9 @@
+{{-- CONTACT FORM --}}
+@php /* @var $form \Cube\Forms\Contact */ @endphp
+
+<div class="spaced">
+  {!! $form->field('last-name', ['show_title' => false]) !!}
+  {!! $form->field('first-name', ['show_title' => false]) !!}
+  {!! $form->field('phone', ['show_title' => false]) !!}
+  {!! $form->button(__('Contactez-moi', 'ccv'), ['class' => 'btn mt-8']) !!}
+</div>
index d20c5d966a6a7720626aa29e8c5d75e92719600c..170bdeab3fb226180889ccbfe9a0d1870ed1fd7b 100644 (file)
 
   </div>
 
-  {!! $form->button(__('Envoyer votre demande', 'ccv'), ['class' => 'btn text-lg block mt-2v mx-auto']) !!}
+  {!!
+    $form->button(__('Envoyer votre demande', 'ccv'), [
+      'class' => 'btn text-lg block mt-2v mx-auto',
+      'loading_text' => __('Envoi en cours...', 'ccv'),
+    ])
+  !!}
 
 </div>
+
+{{-- Custom classes for form message container --}}
+@push('message_class')
+  py-2v pl-4v pr-3v xs:px-2v
+@endpush
index ebffaaf1c9e61dacd7fa383df7af2cec72777b81..c866f85b996378cfb173e6345b7baca44ea31ab9 100644 (file)
@@ -7,8 +7,8 @@
 
 @include('partials.header')
 
-<div class="wrapper flex flex-col min-h-screen">
-  <main class="flex-grow min-h-full bg-white">
+<div class="wrapper w-full flex-grow">
+  <main>
     @yield('content')
   </main>
 
index c2c5d48dff1c4f54d14224bc5fa87840f741d5db..7804bb05220c53fd1c5c9892671062a2b4797d9b 100644 (file)
@@ -1,20 +1,18 @@
-<footer class="site-footer bg-purple-dark text-white antialiased pt-2v pb-1v">
-  <div class="container">
-    <div class="site-footer-cols">
-      <div class="site-footer-col">
-        @php(dynamic_sidebar('sidebar-footer-1'))
-      </div>
-      <div class="site-footer-col">
-        @php(dynamic_sidebar('sidebar-footer-2'))
-      </div>
-      <div class="site-footer-col">
-        @php(dynamic_sidebar('sidebar-footer-3'))
-      </div>
+<footer class="site-footer container bg-purple-dark text-white antialiased pt-2v pb-1v">
+  <div class="site-footer-cols">
+    <div class="site-footer-col">
+      @php(dynamic_sidebar('sidebar-footer-1'))
     </div>
-
-    {{-- Copyright message and links --}}
-    <div class="pl-4v sm:pl-2v pr-2v text-sm" style="color:#9796a0">
-      @php(dynamic_sidebar('sidebar-footer-copyright'))
+    <div class="site-footer-col">
+      @php(dynamic_sidebar('sidebar-footer-2'))
+    </div>
+    <div class="site-footer-col">
+      @php(dynamic_sidebar('sidebar-footer-3'))
     </div>
   </div>
+
+  {{-- Copyright message and links --}}
+  <div class="pl-4v sm:pl-2v pr-2v text-sm" style="color:#9796a0">
+    @php(dynamic_sidebar('sidebar-footer-copyright'))
+  </div>
 </footer>
index 67b1ce05a657f652af812b40daa2e70a4e23586f..f27f7d84903195fa32bf634f6ac23d7aa1f0c2c2 100644 (file)
@@ -42,9 +42,6 @@ mix.browserSync({
   open: false,
 });
 
-// Styles
-mix.stylus(src`styles/app.styl`, 'styles', stylusConfig).options(stylusOptions).tailwind();
-
 // Separate resources for Flatpickr since it is only needed on specific pages
 mix.stylus(src`styles/flatpickr.styl`, 'styles');
 mix.js(src`scripts/flatpickr.js`, 'scripts/flatpickr/trigger.js'); // Trigger function (separated so locales can load first)
@@ -53,26 +50,35 @@ mix.copy('node_modules/flatpickr/dist/flatpickr.min.js', publicPath`scripts/flat
 ['ar', 'fr', 'ru']
   .forEach(locale => mix.copy(`node_modules/flatpickr/dist/l10n/${locale}.js`, publicPath`scripts/flatpickr/locale`));
 
-// JavaScript
-mix.js(src`scripts/app.js`, 'scripts')
-   .js(src`scripts/forms.js`, 'scripts')
-   .js(src`scripts/consultation.js`, 'scripts')
-   .js(src`scripts/header-slideshow.js`, 'scripts')
-   .js(src`scripts/link-carousel.js`, 'scripts')
-   .js(src`scripts/testimonial-carousel.js`, 'scripts')
-   .js(src`scripts/customizer.js`, 'scripts')
-   .extract(['mmenu-light']); // Extract any libraries that will rarely change to a vendor.js file
+// Form handling
+mix.js(src`scripts/forms/forms.js`, 'scripts/forms');
 
 // ParsleyJS validator
-mix.copy('node_modules/parsleyjs/dist/parsley.min.js', publicPath`scripts/parsley`);
+const parsleyPath = publicPath`scripts/forms/parsley`;
+mix.copy('node_modules/parsleyjs/dist/parsley.min.js', parsleyPath);
+mix.js(src`scripts/forms/parsley-setup.js`, parsleyPath);
 // Set which locales to copy
 ['ar', 'fr', 'ru']
-  .forEach(locale => mix.copy(`node_modules/parsleyjs/dist/i18n/${locale}.js`, publicPath`scripts/parsley/locale`));
+  .forEach(locale => mix.copy(`node_modules/parsleyjs/dist/i18n/${locale}.js`, `${parsleyPath}/locale`));
 
 // Lity lightbox
 mix.copy('node_modules/lity/dist/lity.js', publicPath`scripts`);
 mix.stylus(src`styles/components/lity-lightbox.styl`, 'styles/lity.css', stylusConfig).options(stylusOptions).tailwind();
 
+//==================
+
+// Main JavaScript
+mix.js(src`scripts/app.js`, 'scripts')
+  .js(src`scripts/consultation.js`, 'scripts')
+  .js(src`scripts/header-slideshow.js`, 'scripts')
+  .js(src`scripts/link-carousel.js`, 'scripts')
+  .js(src`scripts/testimonial-carousel.js`, 'scripts')
+  .js(src`scripts/customizer.js`, 'scripts')
+  .extract(['mmenu-light']); // Extract any libraries that will rarely change to a vendor.js file
+
+// Main Styles
+mix.stylus(src`styles/app.styl`, 'styles', stylusConfig).options(stylusOptions).tailwind();
+
 // Assets
 mix.copyDirectory(src`images`, publicPath`images`)
    .copyDirectory(src`fonts`, publicPath`fonts`);
@@ -97,6 +103,7 @@ mix.purgeCss({
   extractorPattern: /[A-Za-z0-9-_:!/]+/g, // Tailwind patterns to include classes with special characters like !/:
 
   globs: [
+    path.join(__dirname, './*.php'), // Process root PHP files like index.php and functions.php
     path.join(__dirname, '../../mu-plugins/cube/**/*.php'), // Some classes (eg. for widgets) might be present only here
   ],