How can I make my Ajax add_to_cart work in nested async function

I have out of stock products for which customer can place a preorder. Initialy I am doing this by creating a duplicate product through ajax request by sending product id. Then i am adding this duplicate product (using its id) after response is received by changing add_to_cart button href (the old way). This works correctly. However I wanted to add a notification that shows after checking if the product is in the cart. Could not do that using this method as page reloads in this case. Here is the first working method:

        if(e.target.closest("button[name='pre-order-confirmation-btn']")){
            e.preventDefault()
            e.target.setAttribute('disabled', '')
            const notif_wrapper_in_use = document.querySelector('.stock-notif- wrapper.in-use')
            let btn_clicked = e.target.closest("button[name='pre-order-confirmation-btn']")
            let value = btn_clicked.value
            let a_add_to_cart = notif_wrapper_in_use.querySelector(`a[data-type = ${value}]`)
            let product_id = btn_clicked.dataset.productId
            send_request_create_preorder_product(make_duplicate_preorder_object.ajax_url,'post', product_id, a_add_to_cart, btn_clicked )       
        }

async function send_request_create_preorder_product(url, post, product_id, add_to_cart_btn, btn_clicked){
    let formData = new FormData()
    formData.append('action','send_product_id_for_preorder');
    formData.append('product_id', product_id);
    const options = {
        method : post,
        body : formData
    }
    const response = await fetch(url, options)
    const result = await response.text()
    console.log(result)
    add_to_cart_btn.href = `?add-to-cart=${result}`
    add_to_cart_btn.classList.remove('hide')
    btn_clicked.style.display = 'none'

    /* Here I wanted to add the notification if product is indeed added to cart*/

    if(document.body.classList.contains('home') /*and product in cart*/){
        
/*show successful notification*/
    }

/* ajax function using using hooks wp_ajax and wp_ajax_nopriv*/

function ajax_create_duplicate_product_preorder(){
    if(isset($_POST['action']) && $_POST['action'] === 'send_product_id_for_preorder'){
        $original_product_id = $_POST['product_id'];
        $duplicate_product_id = add_notif_duplicate_wc_product($original_product_id);
        echo $duplicate_product_id;

    }
    wp_die();
}

function add_notif_duplicate_wc_product($product_id){
    $original_product = wc_get_product($product_id);
    if(!$original_product){
        return new WP_Error('invalid_product', 'Invalid product ID.');
    }
    $duplicate_product = new WC_Product_Simple();
    $duplicate_product->set_name($original_product->get_name().'-precommande');
    $duplicate_product->set_status('publish');
    $duplicate_product->set_description($original_product->get_description());
    $duplicate_product->set_short_description($original_product->get_short_description());
    $duplicate_product->set_price($original_product->get_price());
    $duplicate_product->set_regular_price($original_product->get_regular_price());
    $duplicate_product->set_sale_price($original_product->get_sale_price());
    $duplicate_product->set_catalog_visibility('hidden');
    $duplicate_product->set_manage_stock(false);
    $duplicate_product->set_stock_status('instock');
    $duplicate_product->set_virtual(true);
    $duplicate_product_id = $duplicate_product->save();
    $duplicate_product->set_name($duplicate_product->get_name() . '-' . $duplicate_product_id);
    $duplicate_product->save();
    wp_set_object_terms($duplicate_product_id,'preorders', 'product_cat', false);
    $thumbnail_id = get_post_thumbnail_id($product_id);
    if($thumbnail_id){
        set_post_thumbnail($duplicate_product_id, $thumbnail_id);
    }
    // echo $duplicate_product_id;
    return $duplicate_product_id;
}

so as an alternative I tried using this modified async function and woocommerce woocommerce_ajax_add_to_cart (I also removed the ‘post’ parameter in the above async function when called)

async function send_request_create_preorder_product(url, product_id, add_to_cart_btn, btn_clicked) {
    // Create the preorder product via AJAX
    const formData = new FormData();
    formData.append('action', 'send_product_id_for_preorder');
    formData.append('product_id', product_id);

    const response = await fetch(url, { method: 'POST', body: formData });
    const duplicateProductId = await response.text();
    console.log(duplicateProductId)

    if (!duplicateProductId) return;

    // Update the Add to Cart button
    add_to_cart_btn.dataset.productId = duplicateProductId; // store the new product ID
    add_to_cart_btn.classList.remove('hide');
    btn_clicked.style.display = 'none';

   // AJAX Add-to-Cart
    add_to_cart_btn.addEventListener('click', async (e) => {
        e.preventDefault(); // prevent page reload
        const addFormData = new FormData();
        addFormData.append('action', 'woocommerce_ajax_add_to_cart');
        addFormData.append('product_id', duplicateProductId);
        addFormData.append('quantity', 1);

        const addResponse = await fetch(url, { method: 'POST', body: addFormData });
        const addResult = await addResponse.json();
        console.log(addResult)
        if (addResult && addResult.added) {
            // Show notification
            let notifDiv = document.createElement('div');
            notifDiv.className = 'preorder-notif-div-homepage';
            notifDiv.textContent = 'Le produit précommandé a été ajouté au panier';
            document.body.appendChild(notifDiv);
        }
    }, { once: true });
}

The duplicate product_id is properly console logged but then I have a 400 (Bad Request) error on this line const addResponse = await fetch(url, { method: ‘POST’, body: addFormData });
I have been turning around to solve it for 2 days but did not find any working solution.

and this is the HTML form used

                    <div class='stock-notif-wrapper'>
                        <p><button type='button' name='show-add-notif-btn' data-notify-product-id='". $product->get_id(). "'>Pre-commander 24h</button></p>
                        <div class='stock-notif-form-wrapper'>
                            <div class='pre-order-questions-input'>
                                <label>
                                    <input type='radio' name='".$product->get_id()."-"."preorder-or-question[]' class='inputs radios' value='just-ask-date' checked>
                                    Voulez vous savoir quand le produit sera en stock?
                                </label>
                                <label> 
                                    <input type='radio' name='".$product->get_id()."-"."preorder-or-question[]' class='inputs radios' value='send-preorder'>
                                    Voulez-vous payer et pre commander directement?
                                </label>
                            </div>
                            <form action='' method='POST' data-type = 'just-ask-date'>
                                <div class='stock-notif-error-notif-div'></div>
                                <div id='recaptcha-container-". $product->get_id() ."'></div>
                                <div class='notify-inputs-wrapper'>
                                    <input type='email' name='stock-notif-email' class='inputs' placeholder='Email' required>
                                    <input type='tel' name='stock-notif-tel' class='inputs' placeholder='Mobile ex 22 222 222' required>
                                    <input type='hidden' name='notify_product_id' value='{$product->get_id()}'>
                                </div>
                                <div class='button-wrapper'>
                                    <button type='submit' name='stock-notif-submit-btn'>Envoyer</button>
                                    <button type='button' name='show-add-notif-shown-close-btn'>Fermer</button>
                                </div>
                                <div class='company_wrapper'>
                                    <div class='company-addition'>
                                        <input type='text' name='company-name'>
                                    </div>
                                </div>
                                <p class='info'>Vous recevrez un email ou SMS vous informant de la date de disponibilité sous 48h</p>
                            </form>
                            <div class='preorder-notif hide' data-type = 'send-preorder'>
                                <p class='pre-order-description-text'>Vous Allez payer la totalité du produit et nous vous enverrons votre code sous 24h par email si paiement en ligne ou sous 48h si paiement à la livraison</p>
                                <!--<form action='' metod='POST'>-->
                                <button name='pre-order-confirmation-btn' data-product-id ='".$product->get_id()."' value='pre-order-confirmation-add-to-cart'>Confirmer</button>
                                <!--</form>-->
                                <a href='". esc_url( "?add-to-cart=" . $product->get_id() ) . "' class='button' data-type='pre-order-confirmation-add-to-cart'>Pré-commander 24h</a>"."
                                <button type='button' name='show-add-notif-shown-close-btn'>Fermer</button>
                            </div>
                        </div>
                    </div>
                    ";

How externally fetched CSS can be used or it’s source code can be seen for more targeted use? [closed]

In my recent project I copied a CSS source link in my HTML source. Now, using just given class works in my project. But if we are able to read the code, then it we have some scope to fine tune as per our use. Now I have no idea how traditionally developers do ?

At this point I can only try to find out their documentations which I think is unnecessarily time consuming. Sometime it is difficult to find out documentation as well. My need is to look into the source if possible.

Error when importing HydratedRouter and RouterProvider from react-router/dom in TypeScript project

const router = createBrowserRouter([
  {
    path: "/",
    element: <Welcomepage />,
  },
  {
    path: "/signin",
    element: <Signin />,
  },
]);
C:UserschaitOneDriveDesktopdocument3_Coding4-Popxnode_modulesreact-router-domdistindex.mjs:13:48
12 | // index.ts
13 | import { HydratedRouter, RouterProvider } from "react-router/dom";
  >    |                                                ^^^^^^^^^^^^^^^^^^
14 | export * from "react-router";
15 | export {

@parcel/resolver-default: Cannot load file './dom' from module 'react-router'

I am working on a React project and trying to set up routing.
In my index.js file, I wrote:

import { createBrowserRouter, RouterProvider, Outlet } from "react-router";

But I am getting this error:

Error | // index.js > 13 | import { HydratedRouter, RouterProvider } from "react-router/dom";

I installed react-router-dom using:

npm install react-router-dom

function to match a input in array of JSON [closed]

looking for your help on JavaScript function where I want to pass 2 elements as input and expect a true or false as response back.

Scenario – I will be passing below 2 input in Javascript and need to check if value of variable varTaskID i.e. TaskID3 is matching with ExternalTaskId which is in JSON array structure.. if available return as true as simple say false. SO comparing varTaskID with ExternalTaskId element in complete array file.

Input Id – a variable as varTaskID which will have only 1 value like TaskID3
Input 2(A JSON structure where we have a repetitive structure)

{
  "items" :
  [
    {
      "ExternalParentTaskId" : "12345",
      "ExternalTaskId"       : "TaskID1"
    }, 
    {
      "ExternalParentTaskId" : "11111",
      "ExternalTaskId"       : "TaskID2"
    }, 
    {
      "ExternalParentTaskId" : "3456",
      "ExternalTaskId"       : "TaskID3"
    }, 
    {
      "ExternalParentTaskId" : "423423",
      "ExternalTaskId"       : "TaskID3"
    }, 
    {
      "ExternalParentTaskId" : "55666",
      "ExternalTaskId"       : "TaskID3"
    }
  ]
}

Java Script function to match a input in array of JSON

looking for your help on Java script function where I want to ass 2 element as input and expect a true or false as response back.

Scenario – I will be passing below 2 input in Java script and need to check if value of variable varTaskID i.e. TaskID3 is matching with ExternalTaskId which is in JSON array structure.. if available return as true as simple say false. SO comparing varTaskID with ExternalTaskId element in complete array file.

Input Id – a variable as varTaskID which will have only 1 value like TaskID3
Input 2(A JSON structure where we have a repetitive structure)

{
  "items" : [ 
  {
    "ExternalParentTaskId" : "12345",
    "ExternalTaskId" : "TaskID1"
  }, 
  {
    "ExternalParentTaskId" : "11111",
    "ExternalTaskId" : "TaskID2"
  }, 
  {
    "ExternalParentTaskId" : "3456",
    "ExternalTaskId" : "TaskID3"
  }, 
  {
    "ExternalParentTaskId" : "423423",
    "ExternalTaskId" : "TaskID3"
  }, 
  {
    "ExternalParentTaskId" : "55666",
    "ExternalTaskId" : "TaskID3"
  }
  ]
}

Restore scroll position in React after navigating back from a dynamic list

I have a car listing page built with React and React Router. Users can scroll down a list of cars, click a car to view its detail page,
and then hit the browser back button.

The problem: when returning, the page always scrolls to the top instead of the position where the user left off.

The list is dynamic: it loads asynchronously from Firestore, shuffles the cars randomly, and supports pagination and filters. Because of this, scroll restoration happens before the list is fully rendered, so the scroll jumps to the top instead of staying at the previous position.

Using window.history.scrollRestoration = “manual”.

A custom SmartScroll component that saves/restores scroll positions.

React Router v6 ScrollRestoration component.

Expected behavior:
When navigating back from a car detail page, the scroll should restore to the exact position the user left off, even with async data loading, shuffling, and pagination.

// src/main.jsx
import React from "react";
import "./index.css";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import SmartScroll from './utils/SmartScroll';
import { CartProvider } from "./context/CartContext";
import { WishlistProvider } from "./context/WishlistContext";
import { AuthProvider } from "./context/AuthContext";
import { SearchProvider } from "./context/SearchContext";
import { FilterProvider } from "./context/FilterContext";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
  <BrowserRouter>
    <SmartScroll />
    <AuthProvider>
      <CartProvider>
        <WishlistProvider>
          <SearchProvider>
            <FilterProvider>
             <App />
            </FilterProvider>
          </SearchProvider>
        </WishlistProvider>
      </CartProvider>
    </AuthProvider>
  </BrowserRouter>
  </React.StrictMode>
);

// src/App/jsx
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home/Home";
import Cart from "./pages/Cart";
import CarDetails from './pages/CarDetails';
import Navbar from "./components/layout/Navbar";
import Footer from "./components/layout/Footer"; 
import Checkout from "./pages/Checkout"; 
import Wishlist from "./pages/Wishlist";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Success from "./pages/Success";
import About from "./pages/About";
import MyOrders from "./pages/MyOrders";
import ReceiptPage from "./pages/ReceiptPage";
import Inventory from "./pages/Inventory";
import SellYourCar from "./pages/Sellcar";
import Profile from "./pages/Profile";
import EditProfile from "./pages/EditProfile";
import SuccessSold from "./pages/SuccessSold";
import Sold from "./pages/Sold";
import { SoldProvider } from "./context/SoldContext";

function App() {
  return (
    <>
      <Navbar />
      <SoldProvider>
        <Routes>
          {/* Public Routes */}
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/cars" element={<Inventory />} />
          <Route path="/cars/:userId/:carId" element={<CarDetails />} /> 
          <Route path="/login" element={<Login />} />
          <Route path="/signup" element={<Signup />} />
          <Route path="/SuccessSold" element={<SuccessSold />} />

          {/* Protected Routes */}
          <Route path="/profile" element={<Profile />} />
          <Route path="/profile/edit" element={<EditProfile />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="/orders" element={<MyOrders />}/>
          <Route path="/checkout" element={<Checkout />}/>
          <Route path="/edit-car/:userId/:carId" element={<SellYourCar />}/>
          <Route path="/sellcar" element={<SellYourCar />}/>
          <Route path="/sold" element={<Sold />}/>
          <Route path="/wishlist" element={<Wishlist />}/>
          <Route path="/receipt" element={<ReceiptPage />}/>
          <Route path="/success" element={<Success />}/>
        </Routes>
      </SoldProvider>
      <Footer />
    </>
  );
}

export default App;

import { useLayoutEffect, useRef } from "react";
import { useLocation, useNavigationType } from "react-router-dom";

function getScroller() {
  return document.scrollingElement || document.documentElement;
}

export default function SmartScroll({ ready = true }) {
  const location = useLocation();
  const navType = useNavigationType();
  const restored = useRef(false);

  useLayoutEffect(() => {
    if ("scrollRestoration" in window.history) {
      window.history.scrollRestoration = "manual";
    }

    const scroller = getScroller();
    const key = `scroll:${location.key}`;

    const restoreScroll = () => {
      const saved = sessionStorage.getItem(key);
      if (saved) {
        const { x = 0, y = 0 } = JSON.parse(saved);
        scroller.scrollTo({ left: x, top: y, behavior: "auto" });
        restored.current = true;
      }
    };

    if (navType === "POP") {
      // Wait until content is ready
      if (ready) {
        restoreScroll();
      } else {
        const interval = setInterval(() => {
          if (ready && !restored.current) {
            restoreScroll();
            clearInterval(interval);
          }
        }, 50);
        return () => clearInterval(interval);
      }
    } else {
      // New page → scroll to top
      scroller.scrollTo({ left: 0, top: 0, behavior: "auto" });
    }

    // Save scroll on unmount
    return () => {
      const s = getScroller();
      sessionStorage.setItem(key, JSON.stringify({ x: s.scrollLeft, y: s.scrollTop }));
    };
  }, [location, navType, ready]);

  return null;
}

import { useEffect, useState, useLayoutEffect, useRef } from "react";
import { collectionGroup, getDocs, query } from "firebase/firestore";
import { db } from "../../firebase/firebase";
import CarCard from "../../components/cars/CarCard";
import Cambodia from "../../assets/images/logo/Cambodia.png";

export default function CarList({ filters = {}, sortOption }) {
  const [cars, setCars] = useState([]);
  const [carsToDisplay, setCarsToDisplay] = useState(12);
  const containerRef = useRef(null); // New: ref for car grid container

  // Fetch cars
  useEffect(() => {
    const fetchCars = async () => {
      const savedCars = sessionStorage.getItem("carsList");
      if (savedCars) {
        setCars(JSON.parse(savedCars));
        return;
      }

      const carsCollection = collectionGroup(db, "cars");
      const q = query(carsCollection);
      const querySnapshot = await getDocs(q);
      const fetchedCars = querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
      const shuffledCars = fetchedCars.sort(() => Math.random() - 0.5);
      setCars(shuffledCars);
      sessionStorage.setItem("carsList", JSON.stringify(shuffledCars));
    };

    fetchCars();
  }, []);

  const filterAndSortCars = () => {
    let result = [...cars];
    const { brand, condition, location, price, search, type, year } = filters;
    result = result.filter((car) => {
      const matchesSearch =
        !search || `${car.name} ${car.model}`.toLowerCase().includes(search.toLowerCase());
      const matchesLocation = location === "All locations" || car.location === location;
      const matchesBrand = brand === "All brands" || car.name === brand;
      const matchesType = type === "All types" || car.type === type;
      const matchesCondition = condition === "All conditions" || car.condition === condition;
      const matchesYear = year === "All years" || car.year === year;
      const matchesPrice =
        price === "No max" || (price && car.price <= parseInt(price.replace(/D/g, ""), 10));
      return (
        matchesSearch &&
        matchesLocation &&
        matchesBrand &&
        matchesType &&
        matchesCondition &&
        matchesYear &&
        matchesPrice
      );
    });

    switch (sortOption) {
      case "price-asc":
        return result.sort((a, b) => a.price - b.price);
      case "price-desc":
        return result.sort((a, b) => b.price - a.price);
      case "year-desc":
        return result.sort((a, b) => b.year - a.year);
      case "year-asc":
        return result.sort((a, b) => a.year - b.year);
      default:
        return result;
    }
  };

  const filteredCars = filterAndSortCars();
  const carsToShow = filteredCars.slice(0, carsToDisplay);
  const handleViewMore = () => setCarsToDisplay((prev) => prev + 8);

  // ✅ Scroll restoration
  useLayoutEffect(() => {
    const key = "cars-scroll"; // fixed key for CarList
    const scroller = document.scrollingElement || document.documentElement;

    const restoreScroll = () => {
      const saved = sessionStorage.getItem(key);
      if (saved) {
        const { x = 0, y = 0 } = JSON.parse(saved);
        scroller.scrollTo({ left: x, top: y, behavior: "auto" });
      }
    };

    // Only restore when grid has been painted
    if (cars.length > 0) {
      restoreScroll();
    }

    return () => {
      sessionStorage.setItem(
        key,
        JSON.stringify({ x: scroller.scrollLeft, y: scroller.scrollTop })
      );
    };
  }, [cars]);

  return (
    <section className="m-6 mx-2 rounded-[2px] bg-card p-3 px-2 md:m-4 md:p-3 sm:px-10 lg:m-6 lg:mx-10 lg:p-4" ref={containerRef}>
      <div className="mx-auto max-w-7xl">
        <div className="mb-5 flex items-center gap-2">
          <h2 className="text-xl font-bold text-gray-800">Cars for Sale</h2>
          <img src={Cambodia} alt="Cambodia Flag" className="h-7 w-10" />
        </div>

        <div className="grid grid-cols-2 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
          {carsToShow.length > 0
            ? carsToShow.map((car) => <CarCard key={car.id} car={car} />)
            : (
              <div className="col-span-full py-10 text-center text-gray-500">
                <p className="text-lg">No cars found matching your criteria.</p>
                <p className="mt-2 text-sm">Try adjusting your filters or search query.</p>
              </div>
            )}
        </div>

        {filteredCars.length > carsToDisplay && (
          <div className="mt-8 text-center">
            <button onClick={handleViewMore} className="font-base text-[#2384C1] underline">
              View More
            </button>
          </div>
        )}
      </div>
    </section>
  );
}

How can we achieve similar drag-n-drop behaviour of speed dials of opera browser for both sort and group?

I was trying to implement similar drag drop behaviour like opera browser’s speed dial.

  1. Group: It creates new group or add speed dial to existing group when we drag a speed dial over other speed dial or group.
  2. sorting with Smart positioning: Unlike typical sorting behaviour by various sorting libraries, opera browser triggers movement when a speed dial dragged to specific gap positions between cards it seems. It does not trigger movement when we drag a speed dial card over another partially or even completly

I tried Sortable.js and Muuri libs but they triggers movement as soon as the pointer entered into other items boundary and this blocks the possibility of grouping or other actions we can add.

if we want to move card 1 to location of card 2 then we need to drag to opposite side gap not adjusent gap

[ card 1 ] [ card 2 ]
[ card 1 ]|gap1|[ card 2 ]|gap2|

card 1 should be dragged to gap2 then only card 2 will pushed to empty area left by card after dragged.

this allows us to make group by not going to that gap and keep the card 1 over card 2 which will trigger creation of new group with card 1 and card 2.

If you can suggest something about it that will be helpful.

vernabulities in the session cookie

Let’s imagine the user forgets to log out and directly closes the application, so the session ID is still in the browser, and the server has an expiry.If it has the expiry, then if the user is logged in and the cookie expired then does they have to relogin or something else?

I just want to know the answer

“Cannot read property ‘computeModPow’ of null”

I am using aws-identity-js in a React Native Expo project, and when I try to sign in with my Cognito user pool credentials, the sign-in process does not work as expected. Instead, it throws the following error: “Cannot read property ‘computeModPow’ of null.” How can I resolve this issue and successfully authenticate users?

give solution to that problem

Click doesn’t respond on iOS

The click event doesn’t fire on iOS with the following JavaScript. It works fine on Windows. Why is this happening? Is there a workaround?

const pin = new google.maps.marker.PinElement({
    scale: 1.0,
    glyphColor: "green"
});
var testMarker = new google.maps.marker.AdvancedMarkerElement({
    content:        pin.element,
    gmpClickable:   true,
    gmpDraggable:   true
});
testMarker.position = new google.maps.LatLng(
    34.718420, 135.556109
);
testMarker.map = mapObj;
testMarker.addListener(
    'dragend',
    function (event) {
        alert('dragged');
    }
);
testMarker.addListener(
    'click',
    function (event) {
        alert('clicked');
    }
);

On Windows + Chrome, the “clicked” alert is displayed correctly, but on iOS + Chrome (or Safari), nothing happens. Regarding “dragend”, it works as expected in both cases. Using “gmp-click” instead of the “click” event produces the same results. Also, if you don’t add the “dragend” event, the click event works correctly.

How to add an item to the cart only once?

I am working on my store project using JS. I have the addToCard code to add a product to the cart. This code works, but I would like to add the product only once. After which, when I click the button(add-to-cart) again, there was a message – This product is already in the cart. For network requests, I use the Axios library along with Firebase services(Realtime Database, Storage)

export class HomePage extends Component {
  constructor() {
    super();
    this.state = {
      
      orderCart: [],
    };
  }
 
  addToCard = (e) => {
    if (e.target.closest(".add-to-cart")) {
      
      let id = e.target.parentElement.parentElement.dataset.id;
      let price = e.target.parentElement.parentElement.dataset.price;
      let name = e.target.parentElement.parentElement.parentElement.dataset.name;
      let img = e.target.parentElement.parentElement.parentElement.dataset.img;
      
      const cartItems = { id, price, name, img };

      apiService.post("/order", cartItems).then(() => {
       this.setState({
          ...this.state,
          orderCart: this.state.orderCart?.concat(cartItems),
        })
      })
      console.log(id, cartItems);
      
      useToastNotification({
        message: "Product in the cart!",
        type: TOAST_TYPE.success,
      });
    }
  };
  
  componentDidMount() {
    this.addEventListener("click", this.addToCard);
  }

  componentWillUnmount() {
    this.removeEventListener("click", this.addToCard);
  }
}

customElements.define('home-page', HomePage);

For page navigation and for the product card, I use Handlebars. Here is my product card:

{{#each products}}
      <div data-id="{{this.id}}" class="w-full md:w-1/3 xl:w-1/4 p-6 flex flex-col">
        
        <div
          data-img="{{this.img}}"
          data-name="{{this.name}}"
        >
          <img class="hover:grow hover:shadow-lg" src="{{this.img}}" alt="product" />
          <p data-name="{{this.name}}">{{this.name}}</p>
          <div 
            data-id="{{this.id}}"
            data-img="{{this.img}}" 
            data-name="{{this.name}}"
            data-price="{{this.price}}" class="pt-3 flex items-center justify-between">
            <p data-price="{{this.price}}" class="pt-1 text-gray-900">{{this.price}}</p>

            <button
             class="add-to-cart"
             data-id="{{this.id}}"
             data-img="{{this.img}}" 
             data-name="{{this.name}}"
             data-price="{{this.price}}">
             <svg class="h-6 w-6 fill-current text-gray-500 hover:text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
               <path d="M12,4.595c-1.104-1.006-2.512-1.558-3.996-1.558c-1.578,0-3.072,0.623-4.213,1.758c-2.353,2.363-2.352,6.059,0.002,8.412 l7.332,7.332c0.17,0.299,0.498,0.492,0.875,0.492c0.322,0,0.609-0.163,0.792-0.409l7.415-7.415 c2.354-2.354,2.354-6.049-0.002-8.416c-1.137-1.131-2.631-1.754-4.209-1.754C14.513,3.037,13.104,3.589,12,4.595z M18.791,6.205 c1.563,1.571,1.564,4.025,0.002,5.588L12,18.586l-6.793-6.793C3.645,10.23,3.646,7.776,5.205,6.209 c0.76-0.756,1.754-1.172,2.799-1.172s2.035,0.416,2.789,1.17l0.5,0.5c0.391,0.391,1.023,0.391,1.414,0l0.5-0.5 C14.719,4.698,17.281,4.702,18.791,6.205z" />
             </svg>
            </button>
          </div>
        </div>
      </div>
    {{/each}}

I don’t understand how to implement adding a product via ID only once, with this addToCard code

How to log certain Form Responses

I am using the code below from Apps Script ItemResponse documentation. It is triggered by a form response. The trigger and code are working fine, but I only want to log the most recent form response, and I only want to log the responses to certain items only (items 1 through 3). How can I alter the code to do this?

// Open a form by ID and log the responses to each question.
const form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
const formResponses = form.getResponses();
for (let i = 0; i < formResponses.length; i++) {
  const formResponse = formResponses[i];
  const itemResponses = formResponse.getItemResponses();
  for (let j = 0; j < itemResponses.length; j++) {
    const itemResponse = itemResponses[j];
    Logger.log(
        'Response #%s to the question "%s" was "%s"',
        (i + 1).toString(),
        itemResponse.getItem().getTitle(),
        itemResponse.getResponse(),
    );
  }
}

I tried the following code with ran successfully, but nothing was logged. Additionally I’m not sure how to specify I only want responses for items 1 through 3.

  var formResponses = form.getResponses();
  var lastResponse = formResponses[formResponses.length - 1];

  const itemResponses = lastResponse.getItemResponses();
  for {
    const itemResponse = itemResponses[1:3];
    Logger.log(
        'Response #%s to the question "%s" was "%s"',
        (i + 1).toString(),
        itemResponse.getItem().getTitle(),
        itemResponse.getResponse(),
    );
  }

How to fix the Facebook’s “Advertiser Tracking Enabled parameter volume out of range” error

My team wants to run an ad campaign, but the event which we wanna use is not eligible for it, as it says “Advertiser Tracking Enabled parameter volume out of range (Your app needs a certain volume of events with the Advertiser Tracking Enabled (ATE) parameter set as false or no.)”.

We are using appsflyer to send the event from appsflyer to facebook. From server we are sending event to appsflyer then mapping same event to facebook.

Appsflyer says that they pass the ATE internally based on the value of ATT.

In order to fix the issue we are now passing
att , idfa & idva in the event.

Still the event is not yet eligible for the ad.

We followed this doc for the appsflyer event.
https://dev.appsflyer.com/hc/reference/s2s-events-api3-post

Code:


  export const ATT_STATUS = {
   NOT_DETERMINED: 0,
   RESTRICTED: 1,
   DENIED: 2,
   AUTHORIZED: 3,
  };

  const IDFA_VALUES = {
  RESTRICTED: '00000000-0000-0000-0000-000000000000',
  NOT_AVAILABLE: 'Not Available / No Consent',
};

// Determine ATT status based on IDFA value
export const getAttStatus = (idfa) => {
  if (!idfa) return ATT_STATUS.NOT_DETERMINED;

  if (idfa === IDFA_VALUES.RESTRICTED) return ATT_STATUS.RESTRICTED;
  if (idfa === IDFA_VALUES.NOT_AVAILABLE) return ATT_STATUS.DENIED;

  return ATT_STATUS.AUTHORIZED;
};

  async function sendEvent(eventName, customer) {
  try {
    const headers = {
      'Content-Type': 'application/json',
      authentication: configs.appsflyer.apiAccessToken,
    };

    const { metaData } = customer;

    const payload = {
      att: ATT_STATUS.NOT_DETERMINED, // Default value
      eventName,
      customer_user_id: customer?.id,
      appsflyer_id: customer?.appsflyerId,
      eventTime: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'),
      bundleIdentifier: configs.appsflyer.bundleIdentifier,
    };

    // Set IP address (prioritize metaData.ipv4 over customer.ip_address)
    if (metaData?.ipv4) {
      payload.ip = metaData.ipv4;
    } else if (customer?.ip_address) {
      payload.ip = customer.ip_address;
    }

    // Process metaData if available
    if (metaData && typeof metaData === 'object') {
      const idfa = metaData.IDFA;

      if (idfa) {
        payload.idfa = idfa;
        payload.att = getAttStatus(idfa);
      }

      // Add IDFV if available
      if (metaData.IDFV) {
        payload.idfv = metaData.IDFV;
      }
    }

    const response = await axios.post(
      `${configs.appsflyer.apiHost}/inappevent/${configs.appsflyer.appId}`,
      payload,
      { headers }
    );
    return response.data;
  } catch (error) {
    logError(error);
  }
}

After updating the code we have sent around 50+ events, still the event is not eligible, also waited 24hrs still no update.

Has anyone faced this issue? Or does anyone knows how to fix it? Facebook don’t even provide the number of event it needs to track to make it eligible.
Thanks.

TypeError ‘vega-util’ does not resolve to valid URL

I am new to Javascript, Vega-lite, and Rollup. I have been able to solve some errors and unresolved dependencies. I am getting an error in the browser. I do not know what is going on in the bundle js file in the browser.

I am working to create a template or an intial Vega-lite data visualization. This is from the vizhub’s “Fork for Vega-lite api template”.

https://vizhub.com/Dalborg/f5ab04b76aa3483da70c4b58bb635fe7

The error message states: TypeError: Module name, ‘vega-util’ does not resolve to a valid URL.

I have looked up javascript and my the error, but I have not found something that I can deceipher to be relevant to my particular type of error.

I have fixed the circular dependencies by defining some variables for the different vega-lite plugins in the external and plugins (fields?). See the first code block below.

//rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import nodeResolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import commonjs from '@rollup/plugin-commonjs';
import { babel } from '@rollup/plugin-babel';
import nodePolyfills from 'rollup-plugin-polyfill-node';

export default {
    // onwarn: function ( message ) {
    // if (message.code === 'CIRCULAR_DEPENDENCY') {
    //   return;
    // }
    // console.error(message);
    // },
    input:'./main.js',
    output: {
      file:'build/bundle.js',
      format:'iife',
      name: 'MyReactApp',
      globals: {
        'd3': 'd3',
        'vega-embed': 'vega-embed',
        'vega': 'vega',
        'vega-lite': 'vega-lite',
        'vl': 'vl',
        'vega-lite-api': 'vega-lite-api'
      }
    },
    external: [
      'd3',
      'vega-embed',
      'vega-lite',
      'vega-lite-api',
      'vega'
    ],
    plugins: [
      resolve(),
      nodeResolve({
          jsnext:true,
          main:true,
      }),
      replace({
        'process.env.NODE_ENV': JSON.stringify('development'),
        preventAssignment: true,
      }),
      babel({
        babelHelpers: 'bundled',
        extensions: ['.js', '.jsx']
      }),
      commonjs({
        include: 'node_modules/**',
        requireReturnsDefault: 'auto'
      }),
      nodePolyfills(),
    ]
};

This is the second code block retrieved from source plugin in my safari browser.

import { isArray, isString, isObject } from 'vega-util';

var version$1 = "1.0.0";
var pkg = {
    version: version$1};

/**
 * Format the value to be shown in the tooltip.
 *
 * @param value The value to show in the tooltip.
 * @param valueToHtml Function to convert a single cell value to an HTML string
 */
function formatValue(value, valueToHtml, maxDepth, baseURL) {
    if (isArray(value)) {
        return `[${value.map((v) => valueToHtml(isString(v) ? v : stringify(v, maxDepth))).join(', ')}]`;
    }
    if (isObject(value)) {
        let content = '';
        const { title, image, ...rest } = value;
        if (title) {
            content += `<h2>${valueToHtml(title)}</h2>`;
        }
        if (image) {
            content += `<img src="${new URL(valueToHtml(image), baseURL || location.href).href}">`;
        }
        const keys = Object.keys(rest);
        if (keys.length > 0) {
            content += '<table>';
            for (const key of keys) {
                let val = rest[key];
                // ignore undefined properties
                if (val === undefined) {
                    continue;
                }
                if (isObject(val)) {
                    val = stringify(val, maxDepth);
                }
                content += `<tr><td class="key">${valueToHtml(key)}</td><td class="value">${valueToHtml(val)}</td></tr>`;
            }
            content += `</table>`;
        }
        return content || '{}'; // show empty object if there are no properties
    }
    return valueToHtml(value);
}
function replacer(maxDepth) {
    const stack = [];
    return function (key, value) {
        if (typeof value !== 'object' || value === null) {
            return value;
        }
        const pos = stack.indexOf(this) + 1;
        stack.length = pos;
        if (stack.length > maxDepth) {
            return '[Object]';
        }
        if (stack.indexOf(value) >= 0) {
            return '[Circular]';
        }
        stack.push(value);
        return value;
    };
}

I can’t find why nth iterations of an image are not loading

Hi I am trying to create a timeline for my fantasy world, and it works for the most part. You can see it in action here. https://www.skazkaworld.com/timeline.html

As you can see, the background rune image from the 8th entry and beyond does not display correctly. I cannot for the life of me figure out what the issue is. I’m still learning so my code is not very elegant.

Relevant HTML:

<body>
      <div id="navbar"></div><!-- Navbar will be loaded here -->
      <div class="timeline" id="timeline"></div>
    <script src="main.js"></script>
    <script src="timeline.js"></script>
    <script>
        // Load navbar.html dynamically        
        fetch("navbar.html")
            .then(response => response.text())
            .then(data => {
                document.getElementById("navbar").innerHTML = data;

                // Now that navbar is loaded, attach hamburger event
                const hamburger = document.querySelector(".hamburger");
                const navLinks = document.querySelector(".nav-links");

                if (hamburger && navLinks) {
                    hamburger.addEventListener("click", () => {
                        navLinks.classList.toggle("active");
                        hamburger.classList.toggle("open");
                    });
                }
            });
    </script> 
  </body>

CSS:

/* Timeline */ .timeline { 
  display: block; 
  margin: 100px auto 20px; 
  width: 800px; 
  padding: 14px; 
  background: rgba(20, 20, 20, 0.85); 
  height: 80vh; 
  overflow: auto;
  background-image: url("assets/timeline.jpg"); /* replace with your image */ 
  background-repeat: repeat-y; /* repeats vertically */ 
  background-position: center top; /* centers it horizontally */ 
  background-size:cover; /* adjust if needed, e.g., "contain" or specific width */ 
}

.event { 
  margin: 18px 0; 
  cursor: pointer; 

} .event-header { 
  display: flex; 
  align-items: center; 
}

.rune { 
  flex-shrink: 0; 
  width: 28px; 
  height: 28px; 
  margin-right: 8px; 
  background: url("assets/rune-marker.png") center/contain no-repeat; 
  filter: grayscale(100%) brightness(0.9); 
  transition: filter 0.3s ease, transform 0.35s ease; 
}

.rune.active { filter: grayscale(0%) brightness(1.1); 
  transform: scale(1.12); }

.event-details { 
  max-height: 0; 
  overflow: hidden; 
  opacity: 0; 
  transition: max-height 0.4s ease, opacity 0.4s ease; 
  padding-left: 36px; 
  font-size: 0.9em; 
  color: #ccc; 
}

.event.open .event-details { 
  max-height: 200px; 
  opacity: 1; 
  margin-top: 4px; 
}

Finally the JS:

// timeline.js
document.addEventListener("DOMContentLoaded", () => {
  const timeline = document.getElementById("timeline");

  // Timeline data array
  const timelineEvents = [
    {
      year: "1200 AE",
      title: "Founding of the First Ascent",
      details: "The First Ascent arises atop the frozen peaks of Skazka, where mortals defied the giants."
    },
    {
      year: "1345 AE",
      title: "The Rift of Molach",
      details: "A tear in the weave of reality as Molach's fall tore open the land and spirits slipped through."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    // Add more events here...
  ];

  // Generate events dynamically
  timelineEvents.forEach((ev, i) => {
    const eventDiv = document.createElement("div");
    eventDiv.className = "event";
    eventDiv.dataset.id = i;

    eventDiv.innerHTML = `
      <div class="event-header">
        <span class="rune"></span>
        <div>
          <strong>${ev.year}</strong><br>
          ${ev.title}
        </div>
      </div>
      <div class="event-details">
        ${ev.details}
      </div>
    `;

    timeline.appendChild(eventDiv);
  });

  // Scroll rune activation - FIXED to use timeline container scroll
  const events = document.querySelectorAll(".event");
  
  function onTimelineScroll() {
    const timelineRect = timeline.getBoundingClientRect();
    const scrollTop = timeline.scrollTop;
    const viewportMiddle = timelineRect.height / 2;
    
    events.forEach(evt => {
      const eventRect = evt.getBoundingClientRect();
      const timelineTop = timelineRect.top;
      const eventTop = eventRect.top - timelineTop + scrollTop;
      const rune = evt.querySelector(".rune");
      
      // Activate rune if event is in upper half of timeline viewport
      if (eventTop < scrollTop + viewportMiddle) {
        rune.classList.add("active");
      } else {
        rune.classList.remove("active");
      }
    });
  }

  // Toggle event details on click
  events.forEach(evt => {
    evt.addEventListener("click", () => {
      evt.classList.toggle("open");
    });
  });

  // Listen to timeline scroll instead of window scroll
  timeline.addEventListener("scroll", onTimelineScroll);
  onTimelineScroll(); // Initial call
});