Express routes not being hit; router testing middleware never logs requests [closed]

I’m building a Node.js + Express backend with MongoDB and JWT authentication. I have a router userRoutes.js that handles /api/users endpoints, but none of the routes seem to be hit — even a simple console.log in the router middleware never prints.

My setup:

  • server.js
import express from "express";
import cookieParser from "cookie-parser";
import userRoutes from "./routes/userRoutes.js";

const app = express();
app.use(express.json());
app.use(cookieParser());

app.use("/api/users", userRoutes);
app.get("/", (req, res) => res.send("Server is ready"));
app.listen(5000, () => console.log("Server running"));
  • userRoutes.js
import express from "express";
import { registerUser, authUser } from "../controllers/userController.js";

const router = express.Router();

// Debug middleware
router.use((req, res, next) => {
  console.log("Request hit userRoutes:", req.method, req.originalUrl);
  next();
});

router.post("/", registerUser);
router.post("/auth", authUser);

export default router;

Issue:

  • When I make requests to /api/users/ or /api/users/auth, nothing logs in the console.
  • The root route / works fine.

Link to github repo

.env variables setup

MONGO_URI=
JWT_SECRET=
NODE_ENV=development

What I’ve tried:

  • Checked that the paths to the router and controller files are correct.
  • Verified that the server is running and listening on the right port.
  • Ensured express.json() and cookieParser() are used before the router.

Why aren’t requests reaching the userRoutes router? What might be causing the router middleware to never log requests?

The “click” event listener is working but my “keydown” event listener is not

So im making a game, and when i call the function nextDialogue() with a click and keydown event listener which will be active when showDialogue(). the click listener is working. so when the text is currently typing, is actually finished the text first like how i wrote in the skipDialogue() part. but the problem is, when i use a keydown, and battleStarted & isTyping is currently true, it just skipped to showQuestion() immediately without finishing the text.

here is the code i wrote, il give you all of it so you can see it clearly.

let timelineIndex = 1;

function timeline() {
  if (timelineIndex === 1) {
    showDialogue();
  }
  if (timelineIndex === 2) {
    showDialogue();
  }
  if (timelineIndex === 3) {
    setTimeout(() => {
      startBattle();
    }, 2000);
  }
  if (timelineIndex === 4) {
    showDialogue();
  }
}

setTimeout(() => {
  timeline();
}, 2000);

const dialogues = {
  1: [
    { name: "Narrator", text: "Bob and Alice meet at the park, the sun shining brightly.", img: "" },
    { name: "Bob", text: "Glad you made it, Alice! Ready for today’s adventure?", img: "/img/nolan.png" },
    { name: "Narrator", text: "Bob shows a small, worn-out map with odd markings.", img: "" },
    { name: "Alice", text: "That looks mysterious… are we hunting treasure?", img: "/img/eliza.png" }
  ],

  2: [
    { name: "Narrator", text: "They sit on a bench, studying the strange map.", img: "" },
    { name: "Bob", text: "See this mark? Could be a cave… or maybe snacks.", img: "/img/nolan.png" },
    { name: "Alice", text: "If it’s snacks, I’m first in line.", img: "/img/eliza.png" },
    { name: "Narrator", text: "They laugh, folding the map carefully before heading off.", img: "" }
  ],

  3: [
    { name: "Narrator", text: "Walking deeper into the park, Bob balances on a stone wall.", img: "" },
    { name: "Alice", text: "Careful! You’ll trip showing off like that.", img: "/img/eliza.png" },
    { name: "Bob", text: "Relax, I’ve got perfect balance… mostly.", img: "/img/nolan.png" },
    { name: "Narrator", text: "He hops down, and Alice claps at his silly stunt.", img: "" }
  ],

  4: [
    { name: "Narrator", text: "They stop near a huge oak tree, just like on the map.", img: "" },
    { name: "Alice", text: "This has to be our first clue.", img: "/img/eliza.png" },
    { name: "Bob", text: "Only one way to find out!", img: "/img/nolan.png" },
    { name: "Narrator", text: "They circle the tree and spot a small wooden box half-buried.", img: "" }
  ],

  5: [
    { name: "Narrator", text: "Inside the box is a folded note with a short riddle.", img: "" },
    { name: "Bob", text: "Looks like we need to count steps from the fountain.", img: "/img/nolan.png" },
    { name: "Alice", text: "Alright, let’s walk it out together.", img: "/img/eliza.png" },
    { name: "Narrator", text: "They count aloud, stopping at a spot marked on the ground.", img: "" }
  ],

  6: [
    { name: "Narrator", text: "Bob kneels down, pointing at the mark carved into the pavement.", img: "" },
    { name: "Bob", text: "Definitely another clue.", img: "/img/nolan.png" },
    { name: "Alice", text: "Or just a very artistic scratch.", img: "/img/eliza.png" },
    { name: "Narrator", text: "They laugh, snapping a photo before marking it on the map.", img: "" }
  ],

  7: [
    { name: "Narrator", text: "The sun sets as the park grows quiet around them.", img: "" },
    { name: "Alice", text: "That was actually really fun.", img: "/img/eliza.png" },
    { name: "Bob", text: "Yeah. Who knew a map could make the day so exciting?", img: "/img/nolan.png" },
    { name: "Narrator", text: "They sit back on the bench, smiling at the small adventure.", img: "" }
  ],

  8: [
    { name: "Pirate", text: "Haharr! Enough with the child's play! Let's see if ye have the wits for a real pirate's riddle!", img: "/img/pirate.png" },
    { name: "Pirate", text: "Blast it! A lucky guess, ye scurvy dog! But ye'll not be gettin' past this next one!", img: "/img/pirate.png" },
    { name: "Pirate", text: "Shiver me timbers! How'd a landlubber like you figure that out?! This is me final trick!", img: "/img/pirate.png" },
    { name: "Pirate", text: "Arrrgh! Curses! The treasure is yours... for now. But this captain will sail again!", img: "/img/pirate.png" }
  ]
};





let dialogueIndex = 0;
const textElement = document.querySelector(".text");
const nameElement = document.querySelector(".name");
const imgElement = document.querySelector(".textbox img");
const textboxElement = document.querySelector(".textbox-name");
const textBox = document.querySelector(".textbox");

let currentDialogue = 1;
let isTyping = false;
let typingTimeout;
let fullText = "";
let savedCurrentDialogue;

function textTypingEffect(element, text, i = 0) {
  isTyping = true;
  fullText = text;

  return new Promise((resolve) => {
    const textSound = new Audio("/audio/text-blip.mp3");
    document.addEventListener("click", nextDialogue);
    document.addEventListener("keydown", nextDialogue);

    if (i === 0) {
      element.textContent = ""; // clear old text
    }

    element.textContent += text[i];
    textSound.volume = 0.3;
    textSound.play();

    if (i === text.length - 1) {
      isTyping = false;
      resolve();
      return;
    }

    typingTimeout = setTimeout(() => {
      textTypingEffect(element, text, i + 1).then(resolve);
    }, 20);
  });
}

async function showDialogue() {
  // Battle Dialogues

  if (battleStarted === true && savedCurrentDialogue === undefined) {
    savedCurrentDialogue = currentDialogue;
    currentDialogue = 8;
  }

  if (battleStarted === false && savedCurrentDialogue !== undefined) {
    currentDialogue = savedCurrentDialogue;
    currentDialogue++;
  }

  if (dialogueIndex < dialogues[currentDialogue].length) {
    const dialogueDisplay = dialogues[currentDialogue][dialogueIndex].text;
    const nameDisplay = dialogues[currentDialogue][dialogueIndex].name;
    const imgDisplay = dialogues[currentDialogue][dialogueIndex].img;
    
    textBox.classList.remove("hidden");

    if (nameDisplay === "Narrator"){
      textboxElement.classList.add("hidden");
      imgElement.src = imgDisplay;
    } else {
      textboxElement.classList.remove("hidden");
      nameElement.textContent = nameDisplay;
      imgElement.src = imgDisplay;
    }

    await textTypingEffect(textElement, dialogueDisplay);
  } else {
    dialogueIndex = 0;
    currentDialogue++;
    timelineIndex++;
    textBox.classList.add("hidden");
    timeline();
  }
}

// First line
textBox.classList.add("hidden");
// setTimeout(() => {
//   textBox.classList.remove("hidden");
//   showDialogue();
// }, 2000);

function nextDialogue(event) {
  if (event.type === "keydown") {
    if (event.code !== "Space" && event.code !== "Enter") {
      return; // abaikan tombol lain
    }
  }
  document.removeEventListener("click", nextDialogue);
  document.removeEventListener("keydown", nextDialogue);
  skipDialogue();
}

function skipDialogue() {
  if (isTyping) {
    // kalau masih ngetik → langsung tampilkan teks penuh
    clearTimeout(typingTimeout);
    textElement.textContent = fullText;
    isTyping = false;

    // pasang lagi listener supaya bisa lanjut
    document.addEventListener("click", nextDialogue);
    document.addEventListener("keydown", nextDialogue);
    return;
  } 
  if (battleStarted) {
    dialogueIndex++;
    showQuestion();
  } else if (battleStarted === false) {
    // kalau sudah penuh → lanjut ke berikutnya
    dialogueIndex++;
    showDialogue();
  }
}




const questions = {
  1: {
    question: "What is the capital of France?",
    answers: [
      "A. Madrid",
      "B. Berlin",
      "C. Paris",
      "D. Rome"
    ],
    rightAnswer: "C"
  },

  2: {
    question: "Which planet is known as the Red Planet?",
    answers: [
      "A. Venus",
      "B. Mars",
      "C. Jupiter",
      "D. Saturn"
    ],
    rightAnswer: "B"
  },

  3: {
    question: "Who wrote 'Romeo and Juliet'?",
    answers: [
      "A. Charles Dickens",
      "B. William Shakespeare",
      "C. Mark Twain",
      "D. Jane Austen"
    ],
    rightAnswer: "B"
  },

  4: {
    question: "Which is the largest ocean on Earth?",
    answers: [
      "A. Atlantic Ocean",
      "B. Indian Ocean",
      "C. Arctic Ocean",
      "D. Pacific Ocean"
    ],
    rightAnswer: "D"
  }
};

const totalQuestions = Object.keys(questions).length;
let currentQuestion = 1;
let questionCurrentTime = 10;
let questionTimeout;
let battleStarted = false;
const timeText = document.querySelector(".clock-icon p");
const answerElement = document.querySelectorAll(".question-container button");
const clockTick = new Audio("/audio/clock-tick.mp3");


function showQuestion() {
  if (currentQuestion > totalQuestions) {
    battleStarted = false;
    dialogueIndex = 0;
    timelineIndex++;
    timeline();
    return;
  }

  const questionElement = document.querySelector(".question p");
  const questionText = questions[currentQuestion].question;
  const answerText = questions[currentQuestion].answers;

  textBox.classList.add("hidden");

  for (let i = 0; i < answerText.length; i++) {
    answerElement[i].textContent = answerText[i];
  }

  answerElement.forEach(button => {
    button.addEventListener("click", handleButton);
  });

  timeText.textContent = questionCurrentTime;
  questionElement.textContent = questionText;
  questionTimeout = setTimeout(() => {
    if (questionCurrentTime == 0) {
      checkAnswer();
    } 
    else if (questionCurrentTime > 0) {
      questionCurrentTime--;
      timeText.textContent = questionCurrentTime;
      clockTick.play();
      showQuestion();
    }
  }, 1000);
  document.querySelector(".question").classList.remove("hidden");
}

function nextQuestion() {
  dialogueIndex++;
  showQuestion();
}




function checkAnswer(answer) {
  const rightAnswer = questions[currentQuestion].rightAnswer;

  if (!answer) {
    // berarti waktu habis / gak ada jawaban
    const wrongAudio = new Audio("/audio/wrong-answer.mp3");
    wrongAudio.play();

    clearTimeout(questionTimeout);
    questionCurrentTime = 10;
    timeText.textContent = questionCurrentTime;
    answerElement.forEach(button => {
      button.removeEventListener("click", handleButton);
    });
    clockTick.pause();
    clockTick.currentTime = 0;

    setTimeout(() => {
      showQuestion();
    }, 2000);

    return; // stop supaya gak error
  }

  const userAnswer = answer.value;
  const answerContainer = answer.parentElement;

  if (userAnswer !== rightAnswer) {
    answerContainer.style.backgroundColor = "#ff5757";
    const wrongAudio = new Audio("/audio/wrong-answer.mp3");
    wrongAudio.play();

    clearTimeout(questionTimeout);
    questionCurrentTime = 10;
    timeText.textContent = questionCurrentTime;
    answerElement.forEach(button => {
      button.removeEventListener("click", handleButton);
    });
    clockTick.pause();
    clockTick.currentTime = 0;
    setTimeout(() => {
      answerContainer.style.backgroundColor = "";
      showQuestion();
    }, 2000);
  }

  if (rightAnswer === userAnswer) {
    answerContainer.style.backgroundColor = "#48ae51";
    const correctAudio = new Audio("/audio/right-answer.mp3");
    correctAudio.play();
    clearTimeout(questionTimeout);
    questionCurrentTime = 10;
    timeText.textContent = questionCurrentTime;
    answerElement.forEach(button => {
      button.removeEventListener("click", handleButton);
    });
    clockTick.pause();
    clockTick.currentTime = 0;
    currentQuestion++;
    setTimeout(() => {
      answerContainer.style.backgroundColor = "";
      showDialogue();
    }, 1000);

  }
}

function startBattle() {
  battleStarted = true;
  const battle = document.querySelector(".battleStart");
  const battleStartAudio = new Audio("/audio/battle-start.mp3");

  battleStartAudio.play();

  battle.classList.remove("hidden"); // reset animasi kalau sudah ada
  battle.classList.remove("battleTransition"); // reset animasi kalau sudah ada
  void battle.offsetWidth;                     // hack biar animasi bisa replay
  battle.classList.add("battleTransition");

  setTimeout(() => {
    battle.classList.add("hidden");
    showQuestion();
  }, 2000);
} 
// setTimeout(() => {
//   startBattle();
// }, 2000);

function handleButton() {
  checkAnswer(this);
}

Angular get access of dynamic elements and change the values

I have Angular component that contains number of products which are dynamically created using ngFor directive. Here I need to access each products quantity and control using increament and decreament buttons. But this was somewhat critical to do. Can someone suggest the good optimize approach to handle this.
html

<div class="productcontainer">
<ng-container *ngFor="let product of productlists; let i=index">
    <div class="products" *ngIf="product.productPrice > 0">
<h4>{{product.productName}}</h4>
<p>Rs. {{product.productPrice}}</p>
<button mat-icon-button (click)="decrement(i)" color="accent" #buttondecreament>
    <mat-icon>remove</mat-icon>
  </button>
  <mat-form-field  style="width: 50px;">
    <input matInput [(ngModel)]="quantity" size="2" [id]="'quantity_input'+i" #quantityreference="ngModel">
  </mat-form-field>
  <button mat-icon-button (click)="increment(i)" color="accent" #buttonincreament>
    <mat-icon>add</mat-icon>
  </button>
</div>
</ng-container>
</div>

ts file

export class ProductsComponent implements OnInit {
  @ViewChildren('quantityreference') dynamicElements!: QueryList<ElementRef>;
  productlists = JSON.parse(`[
    {
        "productId": "T000",
        "productName": "Punnakuuu",
        "productQuantity": "2",
        "productPrice": "40",
        "gst":"20"
    },
    {
        "productId": "T1111",
        "productName": "Thenkaai Punnaku",
        "productQuantity": "2",
        "productPrice": "40",
        "gst":"18"
    },
    {
        "productId": "MT1",
        "productName": "Mattu Theevanam",
        "productQuantity": "2",
        "productPrice": "55",
        "gst":"18"
    },
    {
        "productId": "CP1",
        "productName": "m Punnaku",
        "productQuantity": "10",
        "productPrice": "55",
        "gst":"12"
    },
    {
      "productId": "CP2",
      "productName": "ell Punnaku",
      "productQuantity": "10",
      "productPrice": "22",
      "gst":"12"
  }]`
  );

  ngtemplatetest: boolean = true;
  quantity: number = 0;
  constructor() { }

  ngOnInit(): void {
    this.ngtemplatetest = true;
  }
  increment(indexvalue: number) {
    this.quantity++;
    let cc = document.getElementById('quantity_input' + indexvalue.toString())
    console.log("the index value is" + cc?.nodeValue);
    
  }
  decrement(indexvalue: number) {
    if (this.quantity > 0) {
      this.quantity--;
    }
  }
}

enter image description here

Currently when I change the quanity it is reflecting in all products. Can someone provide solution

Create scrolling effect on images

I am creating a website for my business using shopify, nothing too fancy.
I am trying to recreate a fun scrolling effect I noticed on : https://www.highsnobiety.com/
The “highsnobiety New York” part a little down the page.
I successfully created the layout (a grid) with correct sizes on desktop and mobile. I added a fade in effect to scroll of the user for the sticky title. All of that is ok.
However I am stuck now on this effect: the images move a bit when you scroll down. As an example, when you scroll way down the page and go up again, the images appear as if stacked (in a tetris like manner not on top of each other).
When inspecting the Highsnobiety page I noticed the elements of the grid have a TranslateY function that updates with each scroll.
Would love to know how I could get close to that effect. My page for now is nice but looks too “flat”.

Here is the code I got (I spared you the schema definition of the shopify section) :

{% style %}
  .scroll-media-section {
    position: relative;
    background-color: {{ section.settings.background_color }};
    min-height: 100vh;
    width: 100%;
    box-sizing: border-box;
    overflow: hidden;
  }

  .scroll-media-container {
    position: relative;
    background-color: {{ section.settings.background_color }};
    padding: 0px 0px;
    width: 100%;
    box-sizing: border-box;
    will-change: transform;
    transition-timing-function:ease-out;
    transition-duration:0.5s;
  }


  .scroll-media-sticky-title {
    position: sticky;
    top: 50%;
    transform: translateY(-50px);
    z-index: 10;
    text-align: center;
    pointer-events: none;
    margin: 0;
    opacity: 0;
    transition: opacity 0.6s ease;
  }

  .scroll-media-sticky-title.visible {
    opacity: 1;
  }

  .scroll-media-title-text {
    font-size: {{ section.settings.title_size }}px;
    color: {{ section.settings.title_color }};
    margin: 0;
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: 2px;
  }

  .scroll-media-title-image {
    max-width: {{ section.settings.logo_max_width }}px;
    height: auto;
  }

  .scroll-media-grid {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: repeat({{ section.settings.columns_desktop }}, 1fr);
    grid-auto-rows: min-content;
    column-gap:8px;
    width: 100%;
    box-sizing: border-box;
  }

  @media (max-width: 749px) {
    .scroll-media-grid {
      grid-template-columns: repeat({{ section.settings.columns_mobile }}, 1fr);
      column-gap:8px;
    }
  }

  .scroll-media-item {
    width: 100%;
    opacity: 0;
    {%comment %}transform: translateY(50px);{%  endcomment %} 
    transition: opacity 0.8s ease, transform 0.8s ease;
    grid-row: auto;
  }

  .scroll-media-item.visible {
    opacity: 1;
    {%comment %}transform: translateY(var(--y-offset-desktop, 0px));{%  endcomment %} 
  }

  @media (max-width: 749px) {
    .scroll-media-item {
      grid-column: var(--mobile-column-start) / span var(--mobile-column-span) !important;
    }
    
    .scroll-media-item.visible {
     {%comment %} transform: translateY(var(--y-offset-mobile, 0px));{%  endcomment %} 
    }
  }

  .scroll-media-item.visible {
    opacity: 1;
    {%comment %}transform: translateY(0);{%  endcomment %} 
  }

  .scroll-media-content {
    width: 100%;
    border-radius: {{ section.settings.media_border_radius }}px;
    overflow: hidden;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  }

  .scroll-media-content img,
  .scroll-media-content video {
    width: 100%;
    height: auto;
    display: block;
  }

  .scroll-media-placeholder {
    background: #000;
    aspect-ratio: 1080/1350;
    display: inline-block;
    justify-content: center;
    align-items: center;
    border-radius: {{ section.settings.media_border_radius }}px;
    position: relative;
  }

  .scroll-media-placeholder svg {
    width: 60%;
    height: 60%;
    opacity: 0.3;
  }

  .scroll-media-empty-state {
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(255,255,255,0.9);
    padding: 6px 12px;
    font-size: 12px;
    color: #333;
    border-radius: 4px;
  }

  html{
    scroll-behavior: smooth;
    }
{% endstyle %}

<scroll-media-section class="scroll-media-section" {{ section.shopify_attributes }}>
  <div class="scroll-media-container">
    
    <!-- Sticky Title -->
    <div class="scroll-media-sticky-title" id="sticky-title-{{ section.id }}" >
      {% if section.settings.title_type == 'text' and section.settings.title_text != blank %}
        <h2 class="scroll-media-title-text">{{ section.settings.title_text }}</h2>
      {% elsif section.settings.title_type == 'image' and section.settings.title_image %}
        <img
          src="{{ section.settings.title_image | image_url: width: 800 }}"
          alt="{{ section.settings.title_image.alt | escape }}"
          class="scroll-media-title-image"
          loading="lazy"
          width="{{ section.settings.title_image.width }}"
          height="{{ section.settings.title_image.height }}"
        >
      {% endif %}
    </div>

    <div class="scroll-media-grid">
      {% for block in section.blocks %}
        {% assign type = block.settings.media_type %}
        {% assign image = block.settings.media_image %}
        {% assign video = block.settings.media_video %}
        {% assign top_margin = block.settings.top_margin %}
        {% assign bottom_margin = block.settings.bottom_margin %}
        {% assign column_start_desktop = block.settings.column_start_desktop %}
        {% assign column_span_desktop = block.settings.column_span_desktop %}
        {% assign column_start_mobile = block.settings.column_start_mobile %}
        {% assign column_span_mobile = block.settings.column_span_mobile %}
        {% assign y_offset = block.settings.y_offset %}
        {% if type != 'none' %}
          <div class="scroll-media-item" 
               style="
                 margin-top: {{ top_margin }}px; 
                 margin-bottom: {{ bottom_margin }}px;
                 grid-column: {{ column_start_desktop }} / span {{ column_span_desktop }};
                 --mobile-column-start: {{ column_start_mobile }};
                 --mobile-column-span: {{ column_span_mobile }};
                 transform: translateY( {{ y_offset }}%);
                 {% comment %}--y-offset-mobile: {{ y_offset_mobile }}%; {% endcomment %}
                 grid-row:{{ forloop.index }};
               "
               data-scroll-item>
            <div class="scroll-media-content">
              {% if type == 'image' and image %}
                <img
                  src="{{ image | image_url: width: 1200 }}"
                  alt="{{ image.alt | escape }}"
                  loading="lazy"
                  width="{{ image.width }}"
                  height="{{ image.height }}"
                >
              {% elsif type == 'video' and video %}
                <video autoplay muted loop playsinline preload="metadata">
                  <source src="{{ video }}" type="video/mp4">
                </video>
              {% else %}
                <div class="scroll-media-placeholder">
                  {{ 'image' | placeholder_svg_tag }}
                  <div class="scroll-media-empty-state">Add media</div>
                </div>
              {% endif %}
            </div>
          </div>
        {% endif %}
      {% endfor %}
    </div>
  </div>
</scroll-media-section>

<script>
  (function() {
    class ScrollMediaSection extends HTMLElement {
      constructor() {
        super();
        this.items = [];
        this.stickyTitle = null;
        this.observer = null;
        this.scrollObserver = null;
        this.container = null;
      }

      connectedCallback() {
        this.items = this.querySelectorAll('[data-scroll-item]');
        this.stickyTitle = this.querySelector('.scroll-media-sticky-title');
        this.container = this.querySelector('.scroll-media-container');
        this.setupIntersectionObserver();
        this.setupScrollObserver();
      }

      disconnectedCallback() {
        if (this.observer) {
          this.observer.disconnect();
        }
        if (this.scrollObserver) {
          window.removeEventListener('scroll', this.scrollObserver);
        }
      }

      setupIntersectionObserver() {
        const options = {
          root: null,
          rootMargin: '-10% 0px -10% 0px',
          threshold: 0.1
        };

        this.observer = new IntersectionObserver((entries) => {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              entry.target.classList.add('visible');
            }
          });
        }, options);

        this.items.forEach((item) => {
          this.observer.observe(item);
        });
      }

      setupScrollObserver() {
        if (!this.stickyTitle || !this.container) return;

        this.scrollObserver = () => {
          const containerRect = this.container.getBoundingClientRect();
          const containerTop = containerRect.top;
          const containerHeight = containerRect.height;
          const scrollPercent = Math.max(0, -containerTop / containerHeight);
          
          if (scrollPercent >= 0.001) {
            this.stickyTitle.classList.add('visible');
          } else {
            this.stickyTitle.classList.remove('visible');
          }
        };

        window.addEventListener('scroll', this.scrollObserver);
      }
    }

    customElements.define('scroll-media-section', ScrollMediaSection);
  })();
</script>

Folder content display like; Subfolder and file display inside a folder [closed]

I am trying to display a subfolders and files inside another folder. I used react.js to create this and zustand to manage state and api calls across components.
My Django API endpoints are working fine. My console.log displays the files and folders but the frontend is not showing this result. How do I make this work.

Here is my code:

  import {useState} from 'react'
  import { useFileStore } from '../../../store/useFileStore'


  cosnt [currentFolder, setCurrentFolder] = useSate(null)
  const {familyFolderFiles, familySubfolder, fetchFamilySubfolders, fetchFamilyFolderFiles} = useFileStore()

  const handleFolderClick = async (folder) => {
    // const famsubfold = await fetchFamilyFolderFiles(memberId, folderId)

    fetchFamilySubfolders(memberId, folder.id)
    fetchFamilyFolderFiles(memberId, folder.id)
    setCurrentFolder(familySubfolder)

    if (folder.id) {
      return navigate(
        `/dashboard/client/${clientId}/family/${memberId}/folders/${folder.id}/subfolders/`
      )
    }
    // return folder.id
  }

I tried fetching the api result and I got that in my console.log but not displaying in my frontend.
I expect that the subfolders and files in a particular folder when I click on it should display.

My cosole.log gives me this: console.log result for folder contents

Programmatically authenticating user in deployed ITop Instance

I have a deployed ITop instance.
I want to allow users to log in through my platform (dotnet web api backend with react frontend) and be directly redirected to itop’s home page according to their user info, surpassing itop’s login.

I’ve read that I need to do this using ITop’s REST API and it needs to be done in the backend server. But after hours of trying, I can not seem to get this API to give me back the token. I also can not find this exact use case documented in ITop’s REST API documentation.

This is my request:
https://<myhosteditoplink>/webservices/rest.php?json_data={"operation": "auth", "auth_user": "admin", "auth_pwd": "itop_pass"}&version=1.3

The response:

{
    "code": 1,
    "message": "Error: Invalid login"
}

Note: I’ve already added a “REST Services User” profile to my admin user through Itop’s dashboard, as shown in the below image.

This is my request: https://<myhosteditoplink>/webservices/rest.php?json_data={"operation": "auth", "auth_user": "admin", "auth_pwd": "itop_pass"}&version=1.3

The response: json { "code": 1, "message": "Error: Invalid login" }

Note: I’ve already added a “REST Services User” profile to my admin user through Itop’s dashboard, as shown in the below image.
admin user with REST api service profile

All products disappear from the page after clicking the “add to cart” button (Java Script)

I am working on a project using JavaScript. Without frameworks. I have a code for adding a product to the cart, this code works, the product is added to the cart. But, after clicking on the button, the entire product disappears from the page, along with the links to the cart and exiting the site. After refreshing the page, the product along with the links appears again. I understand that the problem is in the state. But I don’t understand how to solve it. How to make the product not disappear from the page?

export class HomePage extends Component {
  constructor() {
    super();
    this.template = template();
    this.state = {
      links: [
       {
         label: "Sign In",
         href: ROUTES.singIn,
       },
       {
        label: "My Cart",
        href: ROUTES.singIn,
      },
      ],

      products: [],
      orderCart: [],
    };
  }

  setLinks = () => {
      const { getUser } = useUserStore();
      if (getUser()) {
        this.setState({
          links: [
            {
              label: "My Cart",
              href: ROUTES.cart,
            },
            {
              label: "LogOut",
              href: ROUTES.logOut,
            },
          ],
        });
      }
  };

  getProducts = () => {
    apiService
    .get("/products")
    .then(({ data }) => {
      this.setState({
        ...this.state,
        products: mapResponseApiData(data),
      });
    })
  };

  addToCard = async ({ target }) => {

    const btn = target.closest('.add-to-cart');
    if (!btn) return;
    
    const { id, price, name, img } = btn.dataset;
    if (this.state.orderCart?.some(item => item.id === id)) {
      useToastNotification({ 
        message: 'This product is already in the cart.', 
        type: TOAST_TYPE.info });
      return;
    }

    await apiService.patch(`/order/${id}`, { id, price, name, img });

    this.setState(prev => ({ orderCart: [...(prev.orderCart || []), { id, price, name, img }] }));
    
    useToastNotification({ message: 'Product in the cart!', type: TOAST_TYPE.success });

   };

  componentDidMount() {
    this.setLinks();
    this.getProducts();
    this.addEventListener("click", this.addToCard);
  }

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

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

Previously, I used a different code to add products:

addToCard = (e) => {
    if (e.target.closest(".add-to-cart")) {
      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 = { price, name, img };
      apiService.post("/order", cartItems).then(() => {
        this.setState({
          ...this.state,
          orderCart: this.state.orderCart?.concat(cartItems),
        });
      })
      useToastNotification({
        message: "Product in the cart!",
        type: TOAST_TYPE.success,
      });
    }
  };

This code worked. The product did not disappear from the page, there were no such problems. But, there was a problem with adding the id to the product storage. So I had to redo this code.
For the product storage i use Firebase(RTDB).

Symfony UX – Submit Multiple Nested Component Forms from a Parent Component

I’m working on a Symfony project using Symfony UX, and I’ve run into a challenge regarding forms and nested Live Components.

Imagine this setup:

I have a parent Live Component, and
two nested child components, each with its own independent form (including file uploads).

Normally, each form would have its own submit button, which works fine. However, I would like to manage the submission from a single button in the parent component, which triggers both child forms to submit.

The only approach I’ve tried so far is using component events to coordinate between the parent and children. But this causes several issues:

File uploads are not properly sent when triggering form submissions via events.

If one form has validation errors, I need to manually check and coordinate across both components to halt the saving process for all, even for the one with no errors.

Handling all these events and validation states is getting overly complex and hard to maintain.

So my question is not about how to solve this in code, but more about the logical/architectural approach:

Is there a better or more recommended way to handle this kind of cross-component form submission logic in Symfony UX?

Any suggestions or best practices from those who’ve faced similar issues would be really appreciated.

Thanks!

frxjs/ngx-timeline ‘cannot assign to read only property of object’ errors

I am using angular 16 and I installed
npm I @frxjs/[email protected].
My company has its own artifactory and I uploaded the timeline Library to there after changing the dependency from angular 16 to 17.

I get this error: ” Cannot assign to read only property 0 of object [object array]

The events are got from the store and it seems that I cannot do changes.

Is it a behavior of the timeline or is it because I forced it to work with angular 16?

Thanks in advance

I tried loading the timeline component after the data was initialized but ran to the same problem.

Firebase Cloud Function returns unauthenticated error despite valid client-side token in React Native (Expo)

I’m facing a very confusing bug on a React Native (Expo) project with Firebase. I have a callable Cloud Function (submitRating) that systematically returns an unauthenticated error, even though my client-side code proves that the user is properly logged in.

I have exhausted every debugging path I know and am now completely stuck.

The Problem

The app allows users to rate photos. The voting action calls a Cloud Function.

  • On the client-side: onAuthStateChanged and auth.currentUser both confirm the user is signed in. I can even successfully fetch the ID token (getIdToken) right before making the call.

  • On the server-side: The Cloud Function receives the request, but the context.auth object is null, which triggers my verification error.

My Code

  1. All my services are initialized from the same app instance.
import { initializeApp } from "firebase/app";
import { initializeAuth, getReactNativePersistence } from "firebase/auth";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
import { getFunctions } from "firebase/functions";

const firebaseConfig = {
  apiKey: "XXX",
  authDomain: "XXX.firebaseapp.com",
  projectId: "XXX",
  // ...
};

const app = initializeApp(firebaseConfig);

export const auth = initializeAuth(app, {
  persistence: getReactNativePersistence(AsyncStorage),
});

export const db = getFirestore(app);
export const storage = getStorage(app);
export const functions = getFunctions(app, "us-central1");
  1. Client-Side Call (VoteScreen.js) I’ve confirmed that auth.currentUser is valid before this function is called.
import { httpsCallable } from "firebase/functions";
import { auth, functions } from "../firebaseConfig";

const handleVote = async () => {
  setIsSubmitting(true);
  const currentPhoto = votablePhotos[currentPhotoIndex];

  try {
    console.log("Utilisateur actuel:", auth.currentUser.uid);
    const token = await auth.currentUser.getIdToken();
    console.log("Token JWT récupéré:", token ? "Oui" : "Non");

    const submitRating = httpsCallable(functions, 'submitRating');
    const result = await submitRating({
      photoId: currentPhoto.id,
      challengeId: currentPhoto.challengeId,
      themeRating: 5,
      qualityRating: 5,
    });

    console.log("Succès:", result.data.message);
    goToNextPhoto();

  } catch (error) {
    console.error("Erreur d'appel de la fonction:", error);
    // C'est ici que l'erreur s'affiche
  } finally {
    setIsSubmitting(false);
  }
};
  1. Cloud Function Code (functions/index.js)
const functions = require("firebase-functions");
const admin = require("firebase-admin");

admin.initializeApp();
const db = admin.firestore();

exports.submitRating = functions.https.onCall(async (data, context) => {
  functions.logger.log("Appel reçu. UID de l'authentification :", context.auth ? context.auth.uid : "Non authentifié");

  if (!context.auth) {
    // Cette erreur est systématiquement déclenchée
    throw new functions.https.HttpsError(
      "unauthenticated",
      "Vous devez être connecté pour noter une photo."
    );
  }

  // ... reste de la logique de la fonction ...
  return { success: true, message: "Notation enregistrée !" };
});

What I’m Getting (Logs)

Client-Side Application Console:

LOG Current user UID: iowfvS7nkNfPrTc9OOGVKumUSr42
LOG JWT Token successfully retrieved: Yes
ERROR Error calling function: [FirebaseError: You must be logged in to rate a photo.]

Cloud Function Console:

Call received. Auth UID: Unauthenticated

What I’ve Already Tried

I have been through a long debugging process and can confirm the issue is NOT related to:

  • Billing Plan: The project is on the Blaze (pay-as-you-go) plan.

  • Client-Side Config: The firebaseConfig is correct, and all services are properly initialized from the same app instance.

  • Security Rules: The rules for Storage and Firestore are correctly configured.

  • CORS: The Storage bucket’s CORS policy has been set using gsutil.

  • Function Region: The function’s region (us-central1) is correctly specified in the client-side initialization.

  • App Check: The service is not enforced on Cloud Functions.

  • Dependencies: I have completely reinstalled node_modules and cleared all caches (npm cache clean, expo start -c).

  • The Ultimate Test: The issue persists even on a brand new, clean Firebase project, which is the most confusing part. The manual fetch method also fails with the same error.

Despite all these checks, context.auth remains null on the server-side.

Does anyone have an idea of what could be causing this behavior, possibly related to the Expo environment or a deeper Google Cloud configuration I might be missing?

Thanks in advance for your help!

firebase-analytics cannot find symbol ConsentType.AD_USER_DATA

I’m trying to add @react-native-firebase/analytics to my project, but I got error:

UniversalFirebaseAnalyticsModule.java:132: error: cannot find symbol ConsentType.AD_USER_DATA, adUserData ? ConsentStatus.GRANTED : ConsentStatus.DENIED); ^ symbol: variable AD_USER_DATA location: class ConsentType UniversalFirebaseAnalyticsModule.java:134: error: cannot find symbol ConsentType.AD_PERSONALIZATION, ^ symbol: variable AD_PERSONALIZATION

Javascript code to create comma separated list of all dates between (and including) two dates in YYYYMMDD format [duplicate]

I’m in Glide app and I have two dates that are in YYYYMMDD format; a check in date and check out date.

I need to create a comma separated list of all the dates in between and including those dates.

In Glide, when it uses Javascript, it uses p1, p2 for variables so p1 in the code would reference the column that is the check in date (in YYYYMMDD format) and p2 would so the same but for the check out date.

Would anyone be able to help me here? I’ve tried using chatGPT but I get zero output so I don’t know where it is going wrong.

So for example:

p1 = 20250916
p2 = 20250919

So the output should be

20250916, 20250917, 20250918, 20250919