Serving React application hoping through servers and using iframe

I have a scenario where I have two React applications. Application-1 opens Application-2 using an iframe.

I can access Application-2 either directly or through the Application-1 iframe. However, when I use the Application-1 iframe, it must go through multiple server hops to reach Application-2, something like the following.

Application-1 —-> Express-Server-1(serving Application-1 & forwarding request for Application-2) —-> Express-Server-2(proxy/passthrough) ——> Express-Server-3(serving Application-2)—–> Application-2

while I can access Application-2 directly I can’t access using Application-1 using an Iframe.

so below is the app.js file which serves the Application-2 and is running on server-3.

const createError = require("http-errors");
const express = require("express");
const cors = require('cors')
const path = require("path");
const fs = require("fs");
const cookieParser = require("cookie-parser");

// Server-static middleware
const {
    appLogger,
    expressLogger,
    expressErrorLogger,
} = require("./lib/logger");

const {corsOptionsDelegate} = require("./routes/corsDelegate");
const app = express();

app.disable("x-powered-by");
app.options('*', cors());
// serves static pages
app.use("/application-2/static", express.static(path.join(__dirname, "public")));

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");

app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(expressLogger);
app.use(expressErrorLogger);
app.use(cookieParser());

app.use(
    express.static(path.join(__dirname, ".", "webapp")),
    (req, res, next) => {
        try {
            decodeURIComponent(req.url);
            return next();
        } catch (err) {
            return res.status(400).json({message: "Invalid request"});
        }
    }
);

const managementRouter = require("./routes/management");
app.use("/application-2/management", managementRouter);

// serves the application-2 app
app.use("/application-2", express.static(path.join(__dirname, ".", "webapp")));

// connect to the "src/routes" directory
const routersPath = path.join(__dirname, "routes/v1");
// read all files in the "/src/routers" directory
fs.readdirSync(routersPath).forEach((file) => {
    if (file.endsWith(".js")) {
        // dynamically import the router module
        const routerModule = require((path.join(routersPath, file).replace(".js","")));
        // register the router
        app.use(routerModule);
    }
});

app.get("/application-2/unauthorized", cors(corsOptionsDelegate), function (req, res, next) {
    res.status(403);
    res.sendFile(path.resolve(__dirname, ".", "views", "unauthorized.html"));
});

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next(createError(404));
});

// error handling
app.use(function (err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get("env") === "development" ? err : {};

    // render the error page
    res.status(err.status || 500);
    res.render("error", {title: "ABC Images | application Search"});
});

module.exports = app;

below is code at server-1 to fetch application-2, i.e when application-1 opens iframe it makes a call to server-1 at /application-search.

app.use("/application-search", createProxyMiddleware({
  target: "http://localhost:9012",
  changeOrigin: true,
  logLevel: 'debug',
  pathRewrite: (path, req) => {
    // Extract the query string from the original URL
    const queryString = req.url.split('?')[1] || "11x";
    // Rewrite the path to include the query string
    return `/?${queryString}`;
  },
  onProxyReq: (proxyReq, req, res) => {
    let cookieArray = [];
    try {
      // Split the cookies into an array and log them
      const cookies = req.headers.cookie || "";
      cookieArray = cookies.split(';').map(row => {
        return row.trim();
      });
    } catch (error) {
      console.error('Error processing cookies:', error);
    }
    //const tokenCookie = cookieArray.find(row => row.startsWith('ckns_pp_id=')) || "";
    const tokenValue = tokenCookie.split('=')[1].trim();

    // Add custom headers if needed
    proxyReq.setHeader('Authorization', `Bearer ${tokenValue}`);
  }

}));

below is code at server-2 which when get request from server-1 will hit the server-3 that is serving application-2

app.use("/", createProxyMiddleware({
  target: "https://xxx.co.uk/application-2",
  changeOrigin: true,
  logLevel: 'debug',
  pathRewrite: (path, req) => {
    // Extract the query string from the original URL
    const queryString = req.url.split('?')[1] || "11x";
    // Rewrite the path to include the query string
    return `/?${queryString}`;
  },
  onProxyReq: (proxyReq, req, res) => {
    // Add the authorization header
    const bearerToken = req.headers['authorization'] || "";
    proxyReq.setHeader('Authorization', bearerToken);
  },
  onProxyRes: (proxyRes, req, res) => {
    console.log(`⬅️ Response from Server-3: ${proxyRes.statusCode} for ${req.url}`);
  }
}));

I am either getting 404 not find “Not Found, when from application-1 I try to open iframe to show application-2

404
NotFoundError: Not Found
    at /Users/abc01/projects/application-2/server/app.js:104:10

Currently I am using createProxyMiddleware strategy to pass on the request, please do advise if there is a better strategy for this requirements, as I have to hop though the servers as there are some authentication requirements and can’t access the application-2 directly.

Why is my websocket failing to connect? NextJS -> NGINX -> Scala(akka-http)

Background:

I have a Scala (with akka-http) webserver running behind NGINX. I also have a NextJS application running behind the same NGINX.

My goal here is for the NextJS application to establish a (secure) ws connection with the webserver.

I have followed official documentation and guidelines, and this is my current implementation:

The webserver side:

  val responseQueue: mutable.Queue[ApiResponse] = mutable.Queue()

  private def webSocketFlow: Flow[Message, Message, _] = {
    Flow[Message].mapAsync(1) { _ =>
      if (responseQueue.nonEmpty) {
        system.log.info("Flushing responseQueue")
        val response = responseQueue.dequeue()
        val protobufMessage = ByteString(response.toByteArray)

        Future.successful(BinaryMessage(protobufMessage))
      } else {
        system.log.warn("Response queue empty")
        Future.successful(BinaryMessage(ByteString.empty))
      }
    }
  }

  private def websocketRoute: Route = {
    pathPrefix("ws") {
      pathEndOrSingleSlash {
        extractRequest { req =>
          // extract auth token from header
          val tokenOpt = req.headers.collectFirst {
            case header if header.name() == "Sec-WebSocket-Protocol" =>
              OAuth2BearerToken(header.value()) // Extract the value of the Sec-WebSocket-Protocol header
          }

          system.log.info(s"Handling ws auth:${tokenOpt.toString}")

          // run it through token verification
          extractUri { uri =>
            val callingURI = uri.toRelative.path.dropChars(1).toString

            system.log.info(s"===== $callingURI")

            oAuthAuthenticator(Credentials(tokenOpt), handleWebSocketMessages(webSocketFlow), callingURI).get
          }
        }
      }
    }
  }

  private def oAuthAuthenticator(credentials: Credentials, protectedRoutes: Route, callingURI: String): Option[Route] =
    credentials match {
      case [email protected](_) =>
        system.log.info("Credentials provided")
        val user = loggedInUsers.find(user => p.verify(user.oAuthToken.access_token))
        if (user.isDefined) {
          system.log.info(s"User found:${user.head.toString}")
          val userPermissions = Permissions.valueOf(user.head.user.username.toUpperCase) // <- get permissions for this user

          if (userPermissions.getAllowedRoutes.contains(callingURI)) {
            system.log.info(s"User has permission for route: $callingURI")
            if (user.head.oneTime) loggedInUsers -= user.head // remove token if it's a one-time use token
            Option(protectedRoutes)
          } else {
            system.log.error(s"User does not have permission for route: $callingURI")
            Option(complete(ApiResponse().withStatusResponse(HydraStatusCodes.UNAUTHORISED_ROUTE.getStatusResponse)))
          }
        } else {
          system.log.error("We did not distribute this token or its expired")
          Option(complete(ApiResponse().withStatusResponse(HydraStatusCodes.INVALID_AUTH_TOKEN.getStatusResponse)))
        }
      case _ =>
        system.log.error(s"No credentials provided: ${credentials.toString}")
        Option(complete(ApiResponse().withStatusResponse(HydraStatusCodes.MISSING_CREDENTIALS.getStatusResponse)))
    }

The goal is for the server to notify the web page when a new ApiResponse has been placed in the queue.

I can confirm the Authorization section is working from the webserver logs:

INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - Handling ws auth:Some(Bearer 61704059-2e51-4d0f-b574-bdcebf3aeae3)
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - ===== ws
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - Credentials provided
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - User found:LoggedInUser(database.objects.User@bb81dcf5, username: admin, password: 517ffce87ad701f071040b32ddaa7f4b7b0bb6774b02ff45bf2eef3f2fc1a549,AuthToken(61704059-2e51-4d0f-b574-bdcebf3aeae3,bearer,3600),2025-02-21T16:59:06.343277,false)
INFO[typed-system-actor-akka.actor.default-dispatcher-11] ActorSystem - User has permission for route: ws

On the NextJS side:

    const [pageToken, setPageToken] = useState("")
    const router = useRouter()

    useEffect(() => {
        if (pageToken) {
            const ws = new WebSocket("/ws", pageToken);

            ws.onopen = () => {
                console.log("Connected to WebSocket server");
            };

            ws.onmessage = (event) => {
                try {
                    if (event.data.byteLength === 0) {
                        console.log("No message to decode, queue was empty.");
                        return; // Ignore empty messages
                    }

                    // Deserialize the Protobuf message
                    const buffer = new Uint8Array(event.data);
                    const decodedMessage = ApiResponse.deserializeBinary(buffer)

                    console.log(decodedMessage)
                } catch (err) {
                    console.error("Failed to decode Protobuf message:", err);
                }
            };

            ws.onerror = (err) => {
                console.error("WebSocket error:", err);
            };

            return () => {
                if (ws) {
                    ws.close();
                }
            };
        }
    }, [pageToken]); // <-- listen to token changes

    const fetcher = (url) => fetchWithErrors(url, {}, (error) => {
        if (error.status === 401) {
            setToken(null) //<-- for anything that still might be using token
            setPageToken(null)
            router.push("/login");
        } else {
            errorToast("Unknown Internal Error:" + "[" + error.status + "]" + error.message);
        }
    })
        .then(data => {
            console.log("Fetched token data:", data)
            if (data.access_token) {
                if (pageToken !== data.access_token) {
                    setPageToken(data.access_token);
                    setToken(data.access_token);
                }
            }
            return data
        });

    // read the token every second to ensure it has not expired
    const {data, mutate} = useSWR(
        "/api/gettoken",
        fetcher,
        {
            refreshInterval: 100
        }
    );

    useEffect(() => {
        if (data) return;
        // mark as stale
        mutate()
    }, [data]);

This code:

  1. fetches the token from its own server route (extracts it from a cookie) and sets it globally
  2. When the token changes, establishes a connection with the websocket

The final part of the process is NGINX:

        location /ws {
                proxy_pass http://localhost:8080;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
        }

The Problem:

On my NextJS side, I get the error: Page.jsx:19 WebSocket connection to 'wss://192.168.0.11/ws' failed:

I have captured the output as a HAR which can be found here.

What am I missing/done wrong?

(Shopware 6) Integrating a custom npm package into administration

I want to try to integrate the functionality of the vuedraggable package https://www.npmjs.com/package/vue-draggable into the administration of Shopware 6 (Version 6.6.10.0).

I’ve followed this guide
https://developer.shopware.com/docs/guides/plugins/plugins/plugin-fundamentals/using-npm-dependencies.html , nevertheless I still seem to be getting some errors.

My current files:

Resource/app/administration/build/webpack.config.js:


const { join, resolve } = require('path');

module.exports = () => {
    return {
        resolve: {
            alias: {
                '@chartjs': resolve(
                    join(__dirname, '..', 'node_modules', 'vuedraggable')
                ),
            }
        }
    };
}

I’ve also tried the following example as well:

module.exports = (params) => {
    return { 
        resolve: { 
            modules: [
                `${params.basePath}/Resources/app/storefront/node_modules`,
            ],
       } 
   }; 
}

The administration component I’m trying to override (index.js)


import template from './sw-product-detail.html.twig';
import './sw-product-detail.scss';
import Draggable from "vuedraggable";


const { Criteria } = Shopware.Data
const { Mixin } = Shopware;

Shopware.Component.override('sw-product-detail-base', {
    template,
    components: {
        Draggable
    },

    data() {
        return {
            items: [
                { id: 1, name: "Item 1" },
                { id: 2, name: "Item 2" },
                { id: 3, name: "Item 3" }
            ]
        };
    }
});

The administration component I’m trying to override (twig template)


{% block sw_product_detail_base_media %}
    {% parent() %}
    {% block media_list %}
        <sw-card>
         <h2>Draggable List</h2>
          <Draggable v-model="items" item-key="id">
           <template #item="{ element }">
            <div class="draggable-item">
              {{ element.name }}
            </div>
           </template>
          </Draggable>
        </sw-card>
    {% endblock %}
{% endblock %}

Right now, I’m just trying to reproduce a very simple draggable list. The code above works fine in a standard vue project and produces the following result.
enter image description here

Code example from my custom vue project:


<script>
import Draggable from "vuedraggable";


export default {
  components: {
    Draggable
  },
  data() {
    return {
      items: [
        { id: 1, name: "Item 1" },
        { id: 2, name: "Item 2" },
        { id: 3, name: "Item 3" },
      ],
    };
  },
};
</script>

<template>
  <div>
    <h2>Draggable List</h2>
    <Draggable v-model="items" item-key="id">
      <template #item="{ element }">
        <div class="draggable-item">
          {{ element.name }}
        </div>
      </template>
    </Draggable>
  </div>
</template>

<style>
.draggable-item {
  padding: 10px;
  margin: 5px;
  background-color: lightgray;
  cursor: grab;
}
</style>


Shopware however, seems to struggle with loading the component itself, as I end up having this error message when trying to render the list:

enter image description here

I think my general approach is correct, as I’ve tried it with some other packages, that didnt involve custom vue components and it worked just fine.

Rendering components from other packages however, seems to produce this issue.

Any help would be greatly appreciated!

Thank you in advance.

Why is my saved icon label resets every time i return from a new canvas?

So I am using canvas in react and as i drag and drop my board icon, when i click it, it brings me to a new canvas. On the first canvas(canvas id: 1), I drag and drop my board or with a text that i created. Clicking to that board, i am navigated to a new canvas again (canvas id: 2) and i drag and drop my board icon with some text/label i created. Now, when i turn back to my previous canvas (canvas id: 1), the text/label disappears or prompts me to write the label/text again. Clicking on the board that takes me to canvas id: 2, the text/label disappears as well. It’s like everytime i leave this canvas and return, the saved label/text disappears.

import Navbar from "../components/Navbar"
import { PlusCircleIcon, ArrowUturnLeftIcon, WindowIcon, TrashIcon} from 
'@heroicons/react/24/solid';
import StickyNote2Icon from '@mui/icons-material/StickyNote2';
import {Link, useLocation} from "react-router-dom";
import InfiniteCanvas from "../components/InfiniteCanvas";
import { useState, useEffect, useMemo} from "react";

type IconData = {
  id: string;
  type: string; 
  x: number;
  y: number;
  text: string;
  showInput: boolean;
  isLabel?: boolean;
  showIcon: boolean; 
  dropped?: boolean;
};

type Entry = {
  id: string;
  description: string[]
}

export default function TruthJournal(){
  const [icons, setIcons] = useState<IconData[]>([
    { id: "note", type: "note", x: 50, y: 12, text: "", showInput: false, showIcon: 
true },
    { id: "delicious", type: "delicious", x: 110, y: 10, text: "", showInput: false, 
showIcon: true},
  ]);

  const [showCanvas, setShowCanvas] = useState(true);
  const [canvasId, setCanvasId] = useState(1);
  const [previousCanvasIds, setPreviousCanvasIds] = useState<number[]>([]);
  const location = useLocation();
  const entries: Entry[] = useMemo(() => {
    return location.state?.entries || [];
  }, [location.state?.entries]);  


  const loadIcons = (id: number) => {
    const savedIcons = localStorage.getItem(`icons-${id}`);
    if (savedIcons) {
      setIcons(JSON.parse(savedIcons));
    } else {
      setIcons([]);  
    }
  };


  useEffect(() => {
    const savedCanvasIds = localStorage.getItem("canvas-ids");
    if (savedCanvasIds) {
      const parsedIds = JSON.parse(savedCanvasIds);
      setPreviousCanvasIds(parsedIds);
      setCanvasId(parsedIds[parsedIds.length - 1] || 1);
    }
  }, []);

  useEffect(() => {
    if (canvasId) {
      loadIcons(canvasId);
    }
  }, [canvasId]);

  const handleCreateNewCanvas = () => {
    setCanvasId((prevId) => {
      const newCanvasId = prevId + 1;  

 
      setPreviousCanvasIds((prev) => {
        const updatedIds = [...prev, newCanvasId];
        localStorage.setItem("canvas-ids", JSON.stringify(updatedIds));
        return updatedIds;
      });

      setIcons([]);
      return newCanvasId;
    });

    setShowCanvas(true);

  };



const handleBackClick = () => {
  if (previousCanvasIds.length > 1) {
    const prevIndex = previousCanvasIds.indexOf(canvasId) - 1;
    if (prevIndex >= 0) {
      setCanvasId(previousCanvasIds[prevIndex]);
    }
  }
}

  const handleMouseDown = (e: React.MouseEvent) => {
    console.log("Mouse down", e);
};

const handleMouseMove = (e: React.MouseEvent) => {
    console.log("Mouse move", e);
};

const handleMouseUp = () => {
    console.log("Mouse up");
};

const handleWheel = (e: React.WheelEvent) => {
    console.log("Mouse wheel", e);
};



const handleDragOver = (e: React.DragEvent<HTMLCanvasElement>) => {
  e.preventDefault(); 
};

const handleDrop = (e: React.DragEvent<HTMLCanvasElement>) => {
  e.preventDefault();
  const imageType = e.dataTransfer.getData("image-type");
  const canvasBounds = e.currentTarget.getBoundingClientRect();
  const mouseX = e.clientX - canvasBounds.left;
  const mouseY = e.clientY - canvasBounds.top;

  if (imageType) {
    const newIcon = {
      id: `${imageType}-${Date.now()}`, 
      type: imageType,
      x: mouseX,
      y: mouseY,
      text: "", 
      showInput: imageType === "note",
      isLabel: imageType === "delicious" ,
      showIcon: imageType !== "note", 
      dropped: true
       };

       const updatedIcons = [...icons, newIcon];
    setIcons(updatedIcons);
    localStorage.setItem(`icons-${canvasId}`, JSON.stringify(updatedIcons));

  }


}

const handleDragStart = (e: React.DragEvent<HTMLDivElement>, imageType: string) => {
  e.dataTransfer.setData("image-type", imageType); 
  console.log(`Drag started with type: ${imageType}`);  
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, id: string) => {
  setIcons((prevIcons) =>
    prevIcons.map((icon) =>
      icon.id === id ? { ...icon, text: e.target.value } : icon
    )
  );
};




console.log(showCanvas);

useEffect(() => {
  const savedIcons = localStorage.getItem("icons");
  if (savedIcons) {
    setIcons(JSON.parse(savedIcons));
  }
}, []);

useEffect(() => {
  if (entries.length > 0) {
    setIcons((prevIcons) => {
      const updatedIcons = entries.map((entry, index) => {
        const generateRandomPosition = () => {
          const canvasWidth = window.innerWidth;
      const canvasHeight = window.innerHeight;
      return {
        x: Math.floor(Math.random() * (canvasWidth - 100)),
        y: Math.floor(Math.random() * (canvasHeight - 100)), 
      };
    };

    const isOverlapping = (x: number, y: number, icons: IconData[]) => {
      const margin = 20; 
      return icons.some(
        (icon) =>
          Math.abs(icon.x - x) < margin && Math.abs(icon.y - y) < margin
      );
    };

    let position = generateRandomPosition();
    while (isOverlapping(position.x, position.y, prevIcons)) {
      position = generateRandomPosition();
    }

    return {
      id: `entry-${entry.id || index}`,
      type: "note",
      x: position.x,
      y: position.y,
      text: entry.description.join(", "),
      showInput: true,
      showIcon: false,
      isLabel: false,
      dropped: true,
    };
  });

  return [...prevIcons, ...updatedIcons];
});
  }
}, [entries]); 




return (
    <>
    <div className="bg-customYellow  h-[100vh] ref={divRef} overflow-hidden">
      <Navbar
    Journal='Journal'
    Community = 'Community'
    About = 'About'
     />
     <div  className="bg-customYellow relative top-[3rem] w-full" >

    {showCanvas &&
    (
          <InfiniteCanvas
          width={window.innerWidth}
          height={window.innerHeight}
          onMouseDown={handleMouseDown}
          onMouseMove={handleMouseMove}
          onMouseUp={handleMouseUp}
          onWheel={handleWheel}
          onDragOver={handleDragOver}
          onDrop={handleDrop}
/>
         )}
    
   
    
    <div className="w-full  h-[3em]  flex items-center justify-between pt-5 relative bottom-[40em]">
     
     {icons.map((icon) => (
      <div key={icon.id} className="w-[15%] flex items-center justify-center gap-2 object-contain ">
               
               {icon.showInput ? (
                <input
                  type="text"
                  value={icon.text}
                  onChange={(e) => handleInputChange(e, icon.id)}
                  autoFocus
                  style={{ left: `${icon.x}px`, top: `${icon.y}px` }}
                  className="absolute font-annie pl-3 placeholder-white  text-[12px] text-white border-none bg-customBrown w-[18rem] h-[3rem]"
                  placeholder="Enter text"
                />
              ) : icon.isLabel  && (
                <div
                  className="absolute bottom-0 left-0 text-sm"
                  style={{ top: `${icon.y + 30}px`, left: `${icon.x}px` }}
                >
                 <input
                  type="text"
                  value={icon.text}
                  onChange={(e) => handleInputChange(e, icon.id)}
                  autoFocus
                  className="font-annie placeholder-customBrown text-[12px] text-center border-none text-customBrown  bg-transparent w-[5rem] relative top-3 right-[1.50rem] focus:border-none focus:outline-none"
                  placeholder="Enter text"
                />
                 
                </div>
              )} 
              
               {icon.showIcon && (
                <div
                onClick={handleCreateNewCanvas}
                draggable="true"
                onDragStart={(e) => handleDragStart(e, icon.type)}
                className="w-[2.5rem] h-[2.5rem] object-contain border-box absolute"
                style={{
                  left: `${icon.x}px`,
                  top: `${icon.y}px`,
                  width: "2rem",
                  height: "3rem",
                }}
              >
                {icon.type === "note" ? (
                 <StickyNote2Icon sx={{ width: "2rem", height: "3rem" }} className="w-[2rem] h-[3rem]"/>
                ) : icon.type === "delicious" ? (
                  <WindowIcon className="w-[2rem] h-[3rem]" />
                ) : null}
              </div>
   
      )}
      
                 
                         </div> 

              ))}


              
              <TrashIcon  className="absolute w-[2rem] h-[3rem] right-[83.5%]"/>
              
      
                
     <div className="mr-3 flex gap-3">
     <ArrowUturnLeftIcon onClick={handleBackClick} className="w-8 h-8 fill-customBrown"/>
        <Link to="/journalentrythree">
        <PlusCircleIcon className="w-9 h-9 fill-customBrown"/>
        </Link>
       
        </div>
        </div>
        
   
       
            </div>
           
          
          </div>
          </div>
         
         
      
     
  

    </>
   
)
}

I tried using local storage to save the data but still it resets every time i return to a specific canvas page.

How do I get rid of this warning?: Microsoft Edge is moving towards a new experience that allows users to choose to browse without third-party cookies

I’m making a personal card database for the One Piece Trading Card Game. It’s written in HTML, JavaScrip, and CSS, and when I run the HTML with the “Preview on Web Server” VSCode extension, it gives me exactly 72,819 copies of that error and spins my laptop’s fans while it does so. I’m running it in Microsoft Edge because my school has “Inspect” blocked on Chrome. Aside from that, it is running perfectly. Any advice is appreciated! (Code below)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" type="text/css" href="Search/OP_search.css">
        <title>THE ONE PIIIIEEEECE</title>
        <script src="./Search/OP_search_logic.js" defer></script>
    </head>
    
    <body>
        <form>
            <select name="color" id="colors" oninput="searchCards()">
                <option value="All">All Colors</option>
                <option value="Red">Red</option>
                <option value="Green">Green</option>
                <option value="Blue">Blue</option>
                <option value="Purple">Purple</option>
                <option value="Black">Black</option>
                <option value="Yellow">Yellow</option>
            </select>
            <select name="type" id="types" oninput="searchCards()">
                <option value="All">All Types</option>
                <!--JS will add the rest from JSON-->
            </select>
            <select name="card_type" id="card_types" oninput="searchCards()">
                <option value="All">All Card Types</option>
                <option value="Character">Character</option>
                <option value="Leader">Leader</option>
                <option value="Event">Event</option>
            </select>
            <select name="cost" id="costs" oninput="searchCards()">
                <option value="All">All Costs</option>
                <option value="0">0</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <option value="4">4</option>
                <option value="5">5</option>
                <option value="6">6</option>
                <option value="7">7</option>
                <option value="8">8</option>
                <option value="9">9</option>
                <option value="10">10</option>
            </select>
            <select name="counter" id="counters" oninput="searchCards()">
                <option value="All">All Cards</option>
                <option value="0">0</option>
                <option value=">0">>0</option>
                <option value="1000">1000</option>
                <option value="2000">2000</option>
            </select>
            <select name="set" id="sets" oninput="searchCards()">
                <option value="All">All</option>
                <!--JS will add sets from JSON-->
            </select>
        </form>
        <div id="card_body" class="card_list">
            <!--JS will add displays for individual cards-->
        </div>

    </body>

</html>
function getSearchTerms() {
    //Taking info from selected options in the HTML
    let color     = document.getElementById("colors").value;
    let type      = document.getElementById("types").value;
    let card_type = document.getElementById("card_types").value;
    let cost      = document.getElementById("costs").value;
    let counter   = document.getElementById("counters").value;
    let set       = document.getElementById("sets").value;

    //returning info as a list
    return [card_type, cost, color, type, counter];
};

async function getStorage(file) {
    //Obtaining a JSON
    const response = await fetch(file);
    const json = await response.json();
    return json;
};

async function getDropdowns() {
    let optcgJSON = await getStorage('./Storage/OPTCG.json');

    getTypes(optcgJSON);
    getSets(optcgJSON);
}

function getTypes(optcgJSON) {
    let set = new Set();
    let split;

    //Pulling all unique 'Types' from the JSON
    for (let i = 0; i < optcgJSON.length; i++) {
        split = optcgJSON[i].types.split(';');
        for (let j = 0; j < split.length; j++) {
            set.add(split[j]);
        }
    }

    //Adding new elements to the 'Types' search term (from the JSON)
    let typesSelect = document.querySelector('#types');
    for (i of [...set.values()].sort()) {
        let tempEl = document.createElement('option');
        tempEl.value = i;
        tempEl.innerHTML = i;
        typesSelect.appendChild(tempEl);
    }
};

function getSets(optcgJSON) {
    let set = new Set();

    //Pulling all unique 'Sets' from the JSON
    for (let i = 0; i < optcgJSON.length; i++) {
        set.add(optcgJSON[i].id.split('-')[0]);
    }

    let setSelect = document.querySelector('#sets');
    for (i of [...set.values()].sort()) {
        let tempEl = document.createElement('option');
        tempEl.value = i;
        tempEl.innerHTML = i;
        setSelect.appendChild(tempEl);
    }
};

function displayText(txtid) {

    console.log(txtid);
    let text = document.getElementById(txtid);

    text = text.firstChild;

    if (text.style.display == "none") {
        text.style.display = "block";
    } else {
        text.style.display = "none";
    }

};

function clearElementById(id) {
    document.getElementById(id).innerHTML = "";
};

async function displayCards(arr) {
    //Get the cardBody div
    let cardBody = document.querySelector('#card_body');

    for (i of arr) {

        //temporary div (for the card, to be referenced with CSS)
        let carddiv = document.createElement('div');
        carddiv.class = "card";
        cardBody.appendChild(carddiv);

        //img div
        let imgdiv = document.createElement('div');
        let i_img = document.createElement('img');
        i_img.style.float = "left";
        imgdiv.class = "card_individual";
        i_img.setAttribute("src", i.imageUrl);
        imgdiv.appendChild(i_img);

        //text div
        let txtdiv1 = document.createElement('div');
        txtdiv1.id = `${i.cleanName}`;
        txtdiv1.style = "background-color:#f5f3f1; border-radius: 5vw 5vw 5vw 5vw;";

        let txtdiv2 = document.createElement('div');
        txtdiv2.id = "txtdiv";

        let i_text = document.createElement('p');
        i_text.id = "itxt";
        i_text.style = 'padding-left: 5vw; padding-right: 5vw; font-family: "Courier-New", monospace; font-size: 10pt;';
        i_text.style.display = "none";


        i_img.setAttribute("onclick", `displayText('${txtdiv1.id}')`);

        //Add text
        i_text.innerHTML  = `Name: ${i.name}`;
        i_text.innerHTML += `<br>Id: ${i.id}`;
        i_text.innerHTML += `<br>Card Type: ${i.card_type}`;
        i_text.innerHTML += `<br>Color(s): ${i.colors}`;
        i_text.innerHTML += `<br>Type(s): ${i.types}`;
        if (i.attribute != null)
        i_text.innerHTML += `<br>Attribute: ${i.attribute}`;
        if (i.power != null)
        i_text.innerHTML += `<br>Power: ${i.power}`;
        if (i.counter != null)
        i_text.innerHTML += `<br>Counter: ${i.counter}`;
        if (i.cardText != null)
        i_text.innerHTML += `<br>Effect: ${i.cardText}`;

        txtdiv1.appendChild(i_text);
        txtdiv2.appendChild(txtdiv1);

        carddiv.appendChild(imgdiv);
        carddiv.appendChild(txtdiv1);

    }
};

async function searchCards() {

    clearElementById('card_body');

    let optcgJSON = await getStorage('./Storage/OPTCG.json');

    //0 = card_type, 1 = cost, 2 = color, 3 = type, 4 = counter
    let terms = getSearchTerms();

    //Array of cards that are included in the search
    let includedCards = [];

    //Search logic

    //"optcgJSON" is a global variable (all cards stored as an array of objects)
    for (let i in optcgJSON) {

        //card_type (0)
        if (optcgJSON[i].card_type != terms[0]
         && terms[0] != "All") {
            continue;
        }

        //cost (1)
        if (optcgJSON[i].cost != terms[1]
         && terms[1] != "All") {
            continue;
        }

        //color (2)
        ColorCheck: {
            if (terms[2] == "All") {
                break ColorCheck;
            }
            if (optcgJSON[i].colors.split(';').length == 1) {
                if (optcgJSON[i].colors != terms[2]) {
                    continue;
                }
            } else if (optcgJSON[i].colors.split('/')[0] != terms[2]
                    && optcgJSON[i].colors.split('/')[1] != terms[2] 
                    && terms[2] != "All") {
                continue;
            }
        }

        //type (3)
        let booleanGuy = false;
        TypeCheck: {

            if (terms[3] == "All") {
                break TypeCheck;
            }

            for (j of optcgJSON[i].types.split(';')) {
                if (j == terms[3]) {
                    booleanGuy = true;
                    break;
                }
            }

            if (booleanGuy == false) {
                continue;
            }
        }

        //counter (4)
        booleanGuy = false
        CounterCheck: {
            if (terms[4] == "All") {
                break CounterCheck;
            }
            if (terms[4] == ">0") {
                if (optcgJSON[i].counter > 0) {
                    break CounterCheck;
                } else {
                    continue;
                }
            }

            if (optcgJSON[i].counter != terms[4]) {
                continue;
            }
        }

        includedCards.push(optcgJSON[i]);
    }

    //duh
    displayCards(includedCards);
};

window.onload = () => {
    getDropdowns();
    searchCards();
};
body {
    background-color: #d9d9d9;
    width: 100vw;
}

#txtdiv {
    background-color:white;
    border-radius: 0px 0px 5vw 5vw;
    outline-color: black;
}

#txtdiv p {
    padding-left: 5vw;
    padding-right: 5vw;
    font-family: "Courier-New", monospace;
    font-size: 10pt;
}

.card_list {
    width: 80vw;
    padding-left: 10vw;
    padding-right: 10vw;
    padding-top: 10vw;
    margin: auto;
}

.card_individual {
    width: 100%;
        padding-top: 5vw
}

.card_list img {
    width: 100%;
    margin: auto;
}

I’ll only give one example of what one of the JSON elements looks like, as there are 2000+ items in the JSON. the JSON file contains an array of all of the card objects.

{
  "name": "Trafalgar Law (002)",
  "cleanName": "Trafalgar Law 002",
  "imageUrl": "https://tcgplayer-cdn.tcgplayer.com/product/453505_200w.jpg",
  "cardType": "Leader",
  "rarity": "L",
  "types": "Heart Pirates;Supernovas",
  "id": "OP01-002",
  "color": "Green;Red",
  "life": "4",
  "cardText": "[Activate:Main] [Once Per Turn] (2) <em>(You may rest the specified number of DON!! cards in your cost area.)</em>: If you have 5 Characters, return 1 of your Characters to your hand. Then, play up to 1 Character with a cost of 5 or less from your hand that is a different color than the returned Character.<br><br>rn<a href="https://en.onepiece-cardgame.com/rules/errata_card/#group_20221111-1">This card has been officially errata'd.</a>",
  "cost": null,
  "power": "5000",
  "counter": null,
  "attribute": "Slash",
  "count": ""
}

I’ve sifted through my code, looking for anything that might produce a cookie, but I honestly have no idea what could be doing this. This warning has shown up a few times on occasion, but never to this degree or scale.

I get “JSON invaliInput” error an my arduino project

when i send this data from my web page to ESP8266 Asynwebserver, i receve “Invalid Input Json” error. This in my javascript data: {“tasktId”:”1″,”outputSelected”:”2″,”switchOutputMode”:”on”,”switchModeAfterPower”:false,”startTime”:”18:00″,”endTime”:”06:30″,”dayRepeter”:{“lundi”:true,”mardi”:false,”mercredi”:false,”jeudi”:false,”vendredi”:false,”samedi”:true,”dimanche”:false}}

  server.on("/switch-calendar-mode", HTTP_POST,
  [](AsyncWebServerRequest * request) {}, NULL,
  [](AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total) {
    String receivedData; // Stocke les données reçues
    // Ajoute les nouvelles données
    receivedData.concat((const char*)data, len);

    // Si toutes les données ont été reçues
    if (index + len == total) {
      Serial.println("Données reçues : " + receivedData);
      DynamicJsonDocument doc(2048);
      DeserializationError error = deserializeJson(doc, receivedData);
      if (error) {
        Serial.println("Erreur de parsing JSON: " + String(error.c_str()));
        request->send(400, "application/json", "{"error":"JSON invalide"}");
        return;
      }

      //Reinitialiser pour la prochaine requete
      receivedData = "";
      request->send(200, "application/json", F("{"message":"Données bien reçu."}"));
    }
  });

Vite bundling common js into a single bundle

I have an old project that’s using cjs and we want to switch to vite for bundling all into a iife formatted bundle file.

However vite doesn’t seem to bundle cjs modules even though it’s stated that it’s using the commonjs rollup plugin internally to support cjs.

An example setup:

// src/a.js
function a(chars) {
    console.log('chars', chars);
}

module.exports = a;
// src/index.js
const a = require('./a');

a('hello');

with following config:

// vite.config.js
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
    build: {
        lib: {
            name: 'bundle',
            entry: resolve(import.meta.dirname, 'src', 'index'),
            formats: ['iife'],
        },
    },
});

results in a single bundle file of:

(function(){"use strict";require("./a")("hello")})();

As you can see nothing of the a.js file is included.

NOTE: when switching the example to esm modules vite properly bundles everything.

How can i enable cjs support in my vite setup to support this? Thank you in advance.

Angular update reactive formgroup values

In my Angular application, I have a [formGroup]=”form” and have a submit function which is submitting the form. Currently the form value is being submitted as :

{"customerId": 11,"email": "[email protected]","someId": {
"xid": [
  "AB12456,
  "AB345678"
]},"task": ["admin"]}

I need to change 1 key (someId) to “someIds” and send the form values as follows. What will the best approach for this. Assuming that “someIds” come as values from multiselect dropdown change.

{"customerId": 11, "email": "[email protected]", "someIdsxyz": [
{
  "yId": "abcd1",
  "yType": "abcd2"
},
{
  "yId": "pqrs1",
  "yType": "pqrs2"
}], "task": ["admin"]}

Can anyone suggest?

Django Formset Nested Structure Not Posting Correctly for Dynamic Fields

I’m working on a Django nested formset where users can:

  • Add multiple colors to a product.
  • For each color, add multiple sizes dynamically using JavaScript.
  • Each size should have its own size_name, stock, and price_increment field.

Issue

When submitting the form, Django is incorrectly grouping multiple size field values into lists instead of treating them as separate entries.

Expected Django POST Data (Correct Structure)

sizes-0-0-size_name = "Small"
sizes-0-0-stock = "100"
sizes-0-0-price_increment = "50"

sizes-0-1-size_name = "Medium"
sizes-0-1-stock = "150"
sizes-0-1-price_increment = "75"

Actual Django POST Data (Incorrect Structure)

sizes-0-0-size_name = ["Small", "Medium"]
sizes-0-0-stock = ["100", "150"]
sizes-0-0-price_increment = ["50", "75"]
  • Instead of separate fields for each size, Django is grouping values into a single list.
  • The sizes-0-TOTAL_FORMS field is appearing twice in the POST request, which might indicate a JavaScript duplication issue.

Debugging the Request Data (request.POST)

<QueryDict: {
    'colors-TOTAL_FORMS': ['1'],
    'sizes-0-TOTAL_FORMS': ['1', '1'],  # This should be a single value, not duplicated
    'sizes-0-0-size_name': ['Small', 'Medium'],
    'sizes-0-0-stock': ['100', '150'],
    'sizes-0-0-price_increment': ['50', '75']
}>

Potential Causes:

  1. JavaScript Issue:

    • Dynamic form addition might be incorrectly naming inputs, causing Django to interpret multiple values as a list.
    • TOTAL_FORMS for sizes might not be updated properly, leading to duplicate values.
  2. Django Formset Issue:

    • Django might not be detecting individual size inputs properly due to incorrect prefix handling.

Code Implementation

Forms (forms.py)

class ProductForm(forms.ModelForm):
    class Meta:
        model = VendorProduct
        fields = ['title', 'cagtegory', 'base_price']

class ProductColorForm(forms.ModelForm):
    class Meta:
        model = ProductColor
        fields = ['color_name', 'color_code']

class ProductSizeForm(forms.ModelForm):
    class Meta:
        model = ProductSize
        fields = ['size_name', 'stock', 'price_increment']

ProductColorFormSet = inlineformset_factory(
    VendorProduct, ProductColor, form=ProductColorForm, extra=1, can_delete=True
)
ProductSizeFormSet = inlineformset_factory(
    ProductColor, ProductSize, form=ProductSizeForm, extra=1, can_delete=True
)

View (views.py)

@login_required
def add_product(request):
    if request.method == 'POST':
        product_form = ProductForm(request.POST)
        color_formset = ProductColorFormSet(request.POST, prefix='colors')

        if product_form.is_valid() and color_formset.is_valid():
            product = product_form.save()
            for color_index, color_form in enumerate(color_formset):
                if color_form.cleaned_data.get('color_name'):
                    color = color_form.save(commit=False)
                    color.product = product
                    color.save()

                    # **Check if sizes are structured properly**
                    size_formset = ProductSizeFormSet(
                        request.POST, instance=color, prefix=f'sizes-{color_index}'
                    )
                    print(f"Processing sizes for color index {color_index}:")
                    print(request.POST)

                    if size_formset.is_valid():
                        size_formset.save()

            return redirect('vendorpannel:vendor_shop')

    else:
        product_form = ProductForm()
        color_formset = ProductColorFormSet(prefix='colors')
        color_size_formsets = [
            ProductSizeFormSet(instance=color_form.instance, prefix=f'sizes-{index}')
            for index, color_form in enumerate(color_formset.forms)
        ]

    return render(request, 'vendorpannel/add-product.html', {
        'product_form': product_form,
        'color_formset': color_formset,
        'color_size_formsets': color_size_formsets,
    })

JavaScript for Dynamic Form Handling (add_product.html)

document.addEventListener("DOMContentLoaded", function () {
    let colorIndex = document.querySelectorAll(".color-item").length;
    function addColor() {
        let totalForms = document.querySelector('[name="colors-TOTAL_FORMS"]');
        let newColor = document.querySelector(".color-item").cloneNode(true);
        newColor.querySelectorAll("input").forEach(input => {
            input.name = input.name.replace(/colors-d+/g, `colors-${colorIndex}`);
            input.value = "";
        });

        let sizeContainer = newColor.querySelector(".sizeContainer");
        sizeContainer.innerHTML = "";

        let sizeTotalForms = document.createElement("input");
        sizeTotalForms.type = "hidden";
        sizeTotalForms.name = `sizes-${colorIndex}-TOTAL_FORMS`;
        sizeTotalForms.value = "0";
        sizeContainer.appendChild(sizeTotalForms);

        document.getElementById("colorContainer").appendChild(newColor);
        totalForms.value = colorIndex + 1;
        colorIndex++;
    }

    document.getElementById("addColorButton")?.addEventListener("click", addColor);
});

What I’ve Tried:

✅ Ensured sizes-{colorIndex}-TOTAL_FORMS exists before adding sizes dynamically.
✅ Used name.replace() correctly to update input names.
✅ Verified prefix usage in Django forms and formsets.


Question:

How can I ensure that each size input field gets a unique name instead of Django grouping multiple values into lists?

How to trigger browser extension’s keyboard shortcut in Playwright?

I’m trying to test my browser extension. Pressing Ctrl+Q command injects HTML form into a current page. I want to test this behaviour, but Playwright seems to be unable to trigger it during tests which leads to the test failing. If I press the shortcut manually while the browser instance is running, the form appears correctly. What am I doing wrong?

import test, { chromium, expect } from "@playwright/test";
import path from "path";
import fs from 'fs';

test.use({ browserName: 'chromium' });

test('Open example.com and trigger the popup form', async () => {
  const pathToExtension = path.resolve(__dirname, '..', 'dist');
  const userDataDir = path.resolve(__dirname, '..', 'tmp-profile');

  if (fs.existsSync(userDataDir)) {
    fs.rmSync(userDataDir, { recursive: true, force: true });
  }

  const browserContext = await chromium.launchPersistentContext(userDataDir, {
    headless: false,
    args: [
      `--disable-extensions-except=${pathToExtension}`,
      `--load-extension=${pathToExtension}`
    ]
  });

  const page = await browserContext.newPage();
  await page.goto('https://example.com');
  await page.waitForLoadState('networkidle');

  console.log('Browser launched...');
  
  page.keyboard.press('Control+q'); // doesn't happen

  const popupForm = page.getByLabel('extension-popup-form');
  expect(popupForm).toBeVisible(); // fails here because the form wasn't triggered
  expect(popupForm).toHaveText('https://example.com');

  await popupForm.press('Enter');
  expect(page).toHaveTitle('Blocked | On Pace Extension');

  browserContext.close();
});

I tried this:

  • dispatching event like this:
await page.evaluate(() => {
  document.dispatchEvent(new KeyboardEvent('keydown', {
    key: 'q',
    code: 'KeyQ',
    ctrlKey: true,
    bubbles: true,
    cancelable: true
  }));
});
  • simulated each key press separately in case the automated shortcut was to quick:
  await page.keyboard.down('Control');
  await page.waitForTimeout(800);
  await page.keyboard.press('q');
  await page.waitForTimeout(600);
  await page.keyboard.up('Control');
  • tried focusing on an element of a page before firing the event;
  • tried running the test on https://www.toptal.com/developers/keycode to ensure the codes of pressed keys were correct (they were).
  • tried experimenting with timeout values before running the event:

await page.waitForTimeout(5000);

  • tried manually pressing the command while the browser instance was running to ensure the extension was loaded correctly (it was).

Fetch returning 404 [closed]

The api link is working but for some reason when i try to fetch it, it shows 404 on console

console gets: 404 (Not Found)

const outputElement = document.createElement("h1");
document.body.appendChild(outputElement);

const reqOptions = {
  method: "GET",
  Headers: "Access-Control-Allow-Origin",
};

function getJson(urlApi) {
  fetch(urlApi)
    .then((response) => {
      if (!response.ok) {
        throw new Error("Network is not ok");
      }
      return response.json();
    })
    .then((data) => {
      console.log(data);
      outputElement.textContent = JSON.stringify(data.location);
    })
    .catch((error) => {
      console.error(error);
    });
}

OnTabChange Event not fired on TinyMCE Custom Dialog with TabPanel

With TinyMCE V6, in a custom dialogs, i’d like to handle the tab change event of a tabpanel which got two tabs. I tried to use ‘OnTabChange’, ‘OnChange’ but none of them was never fired. How can i achieve this?

Here’s the code i used:

let activeTab = 'Insert_UGC_From_ID';
const InsertUgcPanel = {
    title: 'Insertion'
    body: {
        type: 'tabpanel',
        tabs: [...]
            }
        ]
    },
    buttons: [{
            type: 'custom',
            name: 'insert-UGC-button',
            text: 'Insérer la chaîne'
        },
        {
            type: 'custom',
            name: 'doesnothing',
            text: 'Annuler'
        }
    ],
    onTabChange: (dialogApi, details) => {
        if (details.name) {
            activeTab = details.name;}
    },  
    onAction: (dialogApi, details) => {
        if (details.name === 'insert-UGC-button') {
            const data = dialogApi.getData()
            dialogApi.close()
        } else if (details.name === 'doesnothing') {
            dialogApi.close()
        }
    }

};

using syncfusion-vue-uploader with vue-3

I am using Vue Syncfusion Uploader (ejs-uploader) to upload files. The upload functionality works fine, and Syncfusion provides a function to handle the selected file.

<template>
  <ejs-uploader
    :buttons="buttonsText"
    id="template"
    name="UploadFiles"
    :multiple="false"
    ref="profileUploader"
    @selected="handleProfilePic"
    @removing="fileDeleted($event, 'profile')"
    :disabled="isView"
  />
</template>

<script setup>
import { ref, onMounted } from "vue";

const fileRecordsForUpload = ref([]);
const profileFiles = ref([]);
const props = defineProps(["rowData"]);

const handleProfilePic = (e) => {
  fileRecordsForUpload.value.push({
    fileName: e.filesData[0].rawFile.name,
    file: e.filesData[0].rawFile,
    originalName: e.filesData[0].rawFile.name,
  });
};

onMounted(() => {
  profileFiles.value = props.rowData.driverDocuments
    .filter((file) => file.documentType === "Driver Picture")
    .map((file) => ({
      name: file.documentName,
      type: "image/jpeg", // Default type
      fileSource: file.contentUrl, // File URL
    }));
});
</script>

After successfully uploading a file, when I open the specific record, all other text data loads correctly, but the previously uploaded file is not displayed in the Syncfusion Uploader.

I tried using v-model, but it didn’t work. How can I prepopulate the Syncfusion Uploader with the previously uploaded file when viewing a record?

Any help would be appreciated!

HTMLCollection does not function for loops [duplicate]

I encountered a problem of making a for loop after a document.getElementsByClassName(“..”)

document.addEventListener('DOMContentLoaded', function() {
let btnTitle = document.getElementsByClassName("button-title");
console.log("Number of button-title elements found:", btnTitle.length);
if (btnTitle) {
    console.log("all button: ", btnTitle);
    Array.from(btnTitle).forEach((btn) => {
        console.log("btn", btn);
        btn.addEventListener("click", (event) => {
            const courseContent = btn.nextElementSibling;
            console.log("courseContent:", courseContent)
            courseContent.classList.toggle('show');
        })
    })
}
}

It will be returning like the image

Image of console

I don’t understand why btnTitle.length return 0, but it still has btnTitle in console, and it has one element who owns a length: 1. I did it because I want to debug why the button’s function is not working

And this is how I create the buttons by javascript (if it’s necessary to debug):

    const courseAndTest = document.getElementById("course_and_test");
if(courseAndTest) {
    //Fetch data
    const student_id = courseAndTest.dataset.studentId;
    fetch(`/student_tests/${student_id}`)
        .then(response => response.json())
        .then(data => {
            if (!data || !data.courses) {
                courseAndTest.innerHTML = "<p>No courses found.</p>";
                return;
            }

            //Make div for each course
            data.courses.forEach(course => {
                let course_nb = 0
                const course_container = document.createElement("div");
                course_container.innerHTML += `<button class="btn btn-primary button-title"><h3>Course ${course_nb += 1}: ${course.course.title}</h3></button>`
                const course_container_2 = document.createElement("div");
                course_container_2.classList.add("toggle-course");
                //Add all lectures of course
                course.lectures.forEach(lecture => {
                    const lecture_container = document.createElement("div");
                    lecture_container.style.overflowX = "auto";
                    lecture_container.innerHTML += `<h4>${lecture.title}</h4>`

                    //create a table with the lecture test score
                    const lecture_table = document.createElement("table");
                    lecture_table.classList.add("lecture-table", "table", "table-hover");
                    //create header for the table
                    lecture_table.innerHTML += `
                    <tr>
                        <th scope="col">Number</th>
                        <th scope="col">Quiz</th>
                        <th scope="col">Score</th>
                    </tr>
                    `
                    lecture_container.appendChild(lecture_table);
                    
                    //Add quize's cell
                    let i = 0
                    lecture.quizes.forEach(quiz => {
                        const tableRow = lecture_table.insertRow();
                        tableRow.insertCell().textContent = i+=1;
                        tableRow.insertCell().textContent = quiz.title;
                        tableRow.insertCell().textContent = quiz.score;
                    });
                    lecture_container.appendChild(lecture_table);
                    course_container_2.appendChild(lecture_container);
                    course_container.appendChild(course_container_2);
                });
                courseAndTest.appendChild(course_container);
            });
        })
        .catch(error => {
            console.error("Error fetching data:", error);
            courseAndTest.innerHTML = "<p>Error loading courses.</p>";
        });

}