]> _ Git - fluidbook-html5.git/commitdiff
wip #6875 @1
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Mon, 22 Apr 2024 09:14:41 +0000 (11:14 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Mon, 22 Apr 2024 09:14:41 +0000 (11:14 +0200)
js/libs/fluidbook/cart/fluidbook.cart.bastide-resah.js
style/cart/bastide-resah-cart.less [new file with mode: 0644]

index 2f24544f7cd67d968f73170b10fe987cddc4e851..dad3fdd8e04bed43f15b09ebbf1fabf0fd04a3a4 100644 (file)
@@ -2,7 +2,7 @@ function FluidbookCartBastideResah(cart) {
     this.cart = cart;
     this.fluidbook = this.cart.fluidbook;
     this.data = this.fluidbook.settings.basketReferences;
-    this.form_endpoint = this.fluidbook.service.getBaseURL()+'bastide'; // Where cart form is processed
+    this.form_endpoint = this.fluidbook.service.getBaseURL() + 'bastide'; // Where cart form is processed
     this.init();
 }
 
@@ -28,7 +28,7 @@ FluidbookCartBastideResah.prototype = {
         });
 
         // Handle "Valider ma sélection" button in cart
-        $(document).on(this.fluidbook.input.clickEvent, '#Bastide_cart [data-validate-cart]', function(event) {
+        $(document).on(this.fluidbook.input.clickEvent, '#Bastide_cart [data-validate-cart]', function (event) {
             event.preventDefault();
 
             // Use the built-in HTML5 validation to make sure all fields are valid in the cart
@@ -42,80 +42,6 @@ FluidbookCartBastideResah.prototype = {
                 window.location.hash = '/cart/validate';
             }
         });
-
-        // Handle details form validation and submission
-        $(document).on(this.fluidbook.input.clickEvent, '#Bastide_user_details [data-send-cart]', function(event) {
-            event.preventDefault();
-
-            let form = $('#Bastide_user_details form:visible');
-
-            if (!form.length) {
-                console.warn('Error finding form...');
-                return false;
-            }
-
-            // Get the form name so that we can look up the field labels later
-            let form_name = form.data('name');
-            let forms = $this.getForms();
-
-            // Get the user's details from the form...
-            let user_data = new FormData(form[0]);
-            let user_details = {}; // Restructured user data to send to the endpoint
-
-            user_data.forEach(function(value, key) {
-                user_details[key] = {
-                    label: forms[form_name][key]['label'],
-                    value: value,
-                };
-            });
-
-            // Prepare the main form data for sending
-            let data = new FormData();
-
-            // To make our endpoint more flexible, we need to give it some extra details
-            data.append('action', 'process-cart'); // There could be other actions handled by the endpoint
-            data.append('fluidbook_id', $this.fluidbook.settings.id); // In case we need special treatment per Fluidbook
-
-            // Now add the user details to the main form data...
-            data.append('user_details', JSON.stringify(user_details));
-
-            // Get data from the cart summary that can be used to generate the XLS
-            // This is passed as a JSON string to make it easier to handle in the FormData
-            data.append('cart_items', JSON.stringify($this.getItemsForXLS()));
-            data.append('column_headings', JSON.stringify($this.getColumnsForXLS()));
-
-            // It's also possible that the Fluidbook will include a querystring parameter (?a=xxxxx) to determine who
-            // the e-mail should be sent to. This is needs to be passed to the endpoint for handling
-            let querystring = (new URL(document.location)).searchParams;
-            data.append('recipient_code', querystring.get('a')); // If the querystring isn't set, it will return null
-
-            // Next, make sure the form is valid. Uses the built-in HTML5 validation
-            if (form[0].reportValidity()) {
-                $.ajax({
-                    url: $this.form_endpoint,
-                    cache: false,
-                    data: data,
-                    processData: false,
-                    contentType: false,
-                    method: 'post',
-                    dataType: 'json',
-                    success: function (data) {
-
-                        if (!data.success) {
-                            console.warn('Error processing request', data);
-                        }
-
-                        let container = $('#Bastide_user_details');
-                        // Hide the form and footer content, then show the message from the endpoint
-                        container.find('.details-form, .details-footer').hide();
-                        container.find('.server-response').text(data.message).show();
-                    }
-                });
-            }
-
-            event.preventDefault();
-        });
-
     },
 
     // emptyCart: function () {
@@ -125,7 +51,7 @@ FluidbookCartBastideResah.prototype = {
     //     resize();
     // },
 
-    getCartItemIndex: function(reference) {
+    getCartItemIndex: function (reference) {
         let cartItems = this.getItems();
         for (let i = 0; i < cartItems.length; i++) {
             if (cartItems[i].reference === reference) {
@@ -201,7 +127,7 @@ FluidbookCartBastideResah.prototype = {
 
             $.each($this.getColumnsForXLS(), function (key, title) {
 
-                switch(key) {
+                switch (key) {
                     case 'QUANTITY':
                         item[key] = cart_item['quantity'];
                         break;
@@ -291,7 +217,7 @@ FluidbookCartBastideResah.prototype = {
         // The columns should be the same as for the cart except we skip "DELETE"
         let columns = {};
 
-        $.each(this.getColumns(), function(key, title) {
+        $.each(this.getColumns(), function (key, title) {
 
             if (key === 'DELETE') return;
 
@@ -338,22 +264,22 @@ FluidbookCartBastideResah.prototype = {
 
     getCartContent: function () {
         if (this.getItemCount() === 0) {
-            return `<div class="cart-empty">${ this.fluidbook.l10n.__('your cart is empty') }</div>`;
+            return `<div class="cart-empty">${this.fluidbook.l10n.__('your cart is empty')}</div>`;
         }
 
         let $this = this;
         let columns = this.getColumns();
 
         let content =
-          `<div class="inner-content">
+            `<div class="inner-content">
              <form>
              <table id="bastide-cart-table" class="cart-items">
                <thead style="text-transform:uppercase">
                  <tr>
                    ${Object.entries(columns)
-                     .map(heading => `<th data-name="${heading[0]}">${heading[1]}</th>`)
-                     .join('')
-                   }
+                .map(heading => `<th data-name="${heading[0]}">${heading[1]}</th>`)
+                .join('')
+            }
                  </tr>
                </thead>
                <tbody>`;
@@ -368,7 +294,7 @@ FluidbookCartBastideResah.prototype = {
                 let value = data[key] || '&mdash;'; // Fallback for missing values
                 let output = '';
 
-                switch(key) {
+                switch (key) {
 
                     case 'PRIX':
                         output += $this.formatPrice(value);
@@ -376,7 +302,7 @@ FluidbookCartBastideResah.prototype = {
                     case 'QUANTITY':
                         let min_quantity = 1;
 
-                        output  = `<div class="quantity-controls">`;
+                        output = `<div class="quantity-controls">`;
 
                         output += `<button onclick="this.parentNode.querySelector('input[type=number]').stepDown();
                                                     this.parentNode.querySelector('input[type=number]').dispatchEvent(new Event('input', { bubbles: true }));
@@ -432,14 +358,14 @@ FluidbookCartBastideResah.prototype = {
         return content;
     },
 
-    getCartUserDetailsContent: function() {
+    getCartUserDetailsContent: function () {
 
         let forms = '';
 
-        $.each(this.getForms(), function(name, fields) {
+        $.each(this.getForms(), function (name, fields) {
             forms += `<form data-name="${name}">`;
 
-            $.each(fields, function(field_name, field_details) {
+            $.each(fields, function (field_name, field_details) {
                 let required = field_details.required ? 'required' : '';
                 let label = field_details.required ? `${field_details.label}*` : field_details.label;
                 let field_ID = `${name}_${field_name}`;
@@ -459,7 +385,7 @@ FluidbookCartBastideResah.prototype = {
         });
 
         let content =
-          `<div class="inner-content" id="Bastide_user_details">
+            `<div class="inner-content" id="Bastide_user_details">
             <div class="details-form">
               ${forms}
               <div class="required-fields-notice">*Champs obligatoires</div>
diff --git a/style/cart/bastide-resah-cart.less b/style/cart/bastide-resah-cart.less
new file mode 100644 (file)
index 0000000..2aba15b
--- /dev/null
@@ -0,0 +1,401 @@
+@breakpoint_table: ~"(max-width: 700px)";
+
+@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@400;700&display=swap');
+
+// Nav override
+.icon-cart {
+  // Display the tooltip label before the cart icon
+  &:before {
+    content: attr(data-tooltip);
+    display: inline-block;
+    text-transform: uppercase;
+    font-size: 12px;
+    font-weight: bold;
+    font-family: 'Roboto Condensed', sans-serif;
+    letter-spacing: 1px;
+    margin-left: 17px;
+    margin-right: -14px;
+    vertical-align: text-bottom;
+  }
+
+  span.number {
+    background-color: #E41C38;
+    top: 1em;
+    right: 1em;
+  }
+}
+
+// Cart modals
+#Bastide_cart {
+  background-color: #fff;
+  color: #000;
+  height: 100%;
+  min-height: 35vh;
+  min-width: 320px;
+  font-size: 18px;
+
+  @media (max-width: 1060px) {
+    font-size: 15px;
+  }
+  @media @breakpoint_table {
+    font-size: 1rem; // Once we're in the stacked view, the font can be a normal size again
+  }
+  @media (max-width: 450px) {
+    font-size: 14px;
+  }
+
+  .caption {
+    padding: 30px 20px;
+    height: auto;
+  }
+
+  #mview-dialog-title {
+    font-size: 16px;
+    font-weight: bold;
+    text-transform: uppercase;
+  }
+
+  > .content .inner-content {
+    width: 94%;
+    margin: 0 auto;
+    position: relative;
+
+    @media @breakpoint_table {
+      width: 90%;
+    }
+  }
+
+  // Main table
+  table.cart-items {
+    width: 100%;
+    max-width: none;
+    margin: 0;
+    border-collapse: collapse;
+    font-size: 0.75em;
+
+    thead {
+      background-color: @menu-background;
+
+      @media @breakpoint_table {
+        display: none;
+      }
+    }
+
+    th {
+      color: #fff;
+      font-weight: bold;
+      text-align: left;
+    }
+
+    th, td {
+      padding: 1.5em 1.75em;
+
+      @media (max-width: 1060px) {
+        padding: 1em 0.5em;
+      }
+    }
+
+    tbody tr {
+      &:hover {
+        background-color: rgba(0, 0, 0, 0.05);
+      }
+
+      @media @breakpoint_table {
+        display: block;
+        padding-top: 1em;
+        border-top: 1px solid;
+      }
+    }
+
+    // Collapse table cells and display header labels before items
+    tbody td {
+      white-space: normal;
+
+      @media @breakpoint_table {
+        display: flex;
+        align-items: center;
+        text-align: left;
+        padding-top: 0;
+
+        &:before {
+          content: attr(data-label) ' : ';
+          font-weight: bold;
+          flex: 0 0 9.5em; // Set width of "label" column in stacked mode
+          text-align: right;
+          padding-right: 10px;
+          line-height: 1;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+
+  // Modal close button
+  .caption .back {
+    background-color: transparent;
+  }
+
+  [data-name="QUANTITY"] {
+    user-select: none; // Stop text being selected when clicking quickly on -/+ buttons
+  }
+
+  .quantity-controls {
+    display: flex;
+    align-items: center;
+  }
+
+  .quantity-button {
+    -webkit-appearance: none;
+    appearance: none;
+    border: none;
+    background: transparent;
+    padding: 0.25em 0.5em;
+    cursor: pointer;
+    height: 16px; // For better vertical alignment with input
+    box-sizing: content-box;
+
+    svg {
+      height: 100%;
+      width: auto;
+    }
+  }
+
+  // Cart item delete buttons
+  [data-name="DELETE"] {
+
+    a {
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      background: #E41C38;
+      width: 2em;
+      height: 2em;
+    }
+
+    svg {
+      width: 0.9em;
+      fill: #fff;
+    }
+
+    @media @breakpoint_table {
+      &:before {
+        content: '' !important; // No label in stacked mode for this item
+      }
+
+      a {
+        width: auto;
+        color: #fff;
+        padding: 0 1em;
+
+        &:before {
+          content: attr(title);
+          text-transform: uppercase;
+          margin-right: 1em;
+        }
+      }
+
+    }
+
+  }
+
+  input[type=email], input[type=number], input[type=text], select, textarea {
+    -webkit-appearance: none;
+    appearance: none;
+    border: 2px solid;
+    border-radius: 0;
+    padding: 0.25em 0.5em;
+    font-size: 1em;
+    width: 100%;
+  }
+  input, select, textarea {
+    background-color: #fff;
+    font-family: inherit;
+    outline: none;
+  }
+
+  select {
+    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13.4 8.1' xml:space='preserve'%3E%3Cpath d='m.7.7 6 6 6-6' fill='none' stroke='%231c1c1c' stroke-width='2'/%3E%3C/svg%3E");
+    background-position: calc(100% - 0.6em) 50%;
+    background-repeat: no-repeat;
+    background-size: 0.85em auto;
+    padding-right: 1.8em;
+
+    &.alert, &:invalid { // Warning for unset colour selection
+      color: red;
+      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13.4 8.1' xml:space='preserve'%3E%3Cpath d='m.7.7 6 6 6-6' fill='none' stroke='red' stroke-width='2'/%3E%3C/svg%3E");
+    }
+
+    @media @breakpoint_table {
+      width: max-content; // When switching to stacked view, select boxes should only be as wide as their content
+    }
+  }
+
+  // Quantity inputs - hide browser up/down arrows
+  input::-webkit-outer-spin-button,
+  input::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+    margin: 0;
+  }
+
+  input[type=number] {
+    -moz-appearance: textfield;
+  }
+
+  input[data-item-quantity] {
+    width: 2.5em;
+    text-align: center;
+    padding: 0.25em 0.25em;
+  }
+
+  // Cart footer
+  tfoot.cart-footer {
+
+    // Extra spacing between table footer and table body
+    // Margins don't work in the middle of a table and we need to ensure the columns structure remains consistent
+    &:before {
+      content: '';
+      display: table-row;
+      height: 1em;
+    }
+
+    // Border + extra padding on the first row of cells
+    // Once again, this is more convoluted because we can't use margins in the table
+    tr:first-of-type td {
+      padding-top: 1.5em;
+      border-top: 2px solid;
+    }
+
+    td {
+      padding-top: 0.5em;
+      padding-bottom: 0.5em;
+      text-align: right;
+    }
+
+    // When cart popup goes into "full screen" / cover mode
+    [data-menu="cart"].fs & {
+      top: unset;
+      position: unset; // This needs to be unset so .exclusivity-notice remains relative to .content-inner
+    }
+
+    // Responsive overrides for small screens
+    @media @breakpoint_table {
+      tr {
+        display: flex;
+      }
+
+      td {
+        // Middle columns that contain labels and totals
+        // The fixed size makes them centred, while other cells flex to take space on left and right
+        // Their size is set in em units so that it works regardless of the actual font size
+        flex: 0 0 7.5em; // The flex-basis might need to be adjusted if labels or values get any wider
+
+        &:first-child, &:last-child {
+          flex: 1;
+        }
+      }
+    }
+
+  }
+
+  .fonctions {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 1em;
+    padding: 2em 0;
+
+    // Reorganise footer for better display and wrapping
+    @media @breakpoint_table {
+      flex-wrap: wrap;
+      flex-direction: row-reverse;
+      justify-content: flex-start;
+    }
+
+    a {
+      background-color: @menu-background;
+      color: #fff;
+      white-space: nowrap;
+      margin: 0;
+
+      @media @breakpoint_table {
+        flex: 1; // Allows items to grow to maximise width
+      }
+    }
+  }
+
+  .validate-cart, .send-cart {
+    min-width: 30%;
+    font-weight: bold;
+
+    @media @breakpoint_table {
+      min-width: unset; // Without resetting this, the flex items weren't wrapping
+    }
+  }
+
+  //==== User details modal
+
+  #Bastide_user_details {
+    width: 90% !important;
+
+    .details-form {
+      font-size: 0.75em;
+    }
+
+    form {
+      max-width: 680px;
+      margin-left: auto;
+
+      display: grid;
+      /* 1st column should be max-content but it can shrink if needed... */
+      /* 2nd column should take remaining width but not get smaller than 15em */
+      grid-template-columns: minmax(min-content, max-content) minmax(15em, 1fr);
+      text-align: right; /* Right align for labels */
+      grid-row-gap: 0.5em;
+      grid-column-gap: 1em;
+
+      @media (max-width: 550px) {
+        grid-template-columns: 1fr;
+        text-align: left;
+      }
+
+      label {
+        text-transform: uppercase;
+        margin-top: 0.4em;
+        margin-bottom: -0.2em; // Only really applies when form is 1 column
+      }
+
+    }
+  }
+
+  .required-fields-notice {
+    margin-top: 1em;
+    text-align: right;
+
+    @media @breakpoint_table {
+      position: initial;
+      transform: none;
+      flex-basis: 100%;
+    }
+  }
+
+  .back-to-cart, .close-cart {
+    background-color: transparent !important;
+    border: 2px solid currentColor;
+    color: @menu-background !important;
+    display: inline-flex;
+    font-weight: bold;
+    align-items: center;
+    margin: 0;
+    justify-content: center;
+
+    @media @breakpoint_table {
+      order: 3; // Switch position (since we're in flex-direction: row-reverse)
+    }
+  }
+
+  .server-response {
+    max-width: 50ch;
+    margin: 2em auto 0;
+  }
+
+}