Socket IO – issues when sending messages

I’m new to Socket IO, and I’m currently building a real time chat application with **ReactJS **and Socket IO.
Users can select from a list of conversations they already have with users to chat in real time, or they can create new conversations with new people. Here is how the system currently works:

This code is ran after a user sends a message in a chat room with another user.

// Client
socket.emit("send message", data)
// Server 

// Stores connected users
var usersArr= []

//(otherUsers variable comes from data passed from client)
socket.on("send message", data => {
    const otherUsers = data.users
    // Map over all users in the conversation/chat, and send them the data
    otherUsers.forEach(u => {
       // Find user in sockets array on server
       const otherUser = usersArr.find(user => user.id == u.id)
       // Send to user
       io.to(otherUser.socketId).emit("receive_message", messageData)
    })
})

Then on the client, I have the following event to receive messages. This code below is located on the ChatScreen where users can message each other. Chat screen holds a state value determining which conversation to display and chat in


// Client (On chat screen)

// Messages between two users stored in state and displayed
const [messages, setMessages] = useState([])

// Add new message to Chat screen's state after receiving message
socket.on("receive_message", (messageData) => {
   setMessages([...messages, messageData.msg])
})

// Display all messages from the chat between the users
{messages.map(m => {
    return <p>{m.msg}</p>
})}

This code works fine, however there are a few issues I encountered.

Issue 1 is If the user is not currently on the chat screen, how could they view the message? What would I have to implement to perhaps show a counter on the messages icon displayed on the home screen, indicating how many unread, received messages a user has. As currently, after receiving a message, it’s appended to ChatScreen’s state. However, this isn’t possible if the user is on another page or even on the ChatScreen with a different user/conversation. Users should still have a way to know that a message has been sent. I.e a counter indicator as mentioned above.

Issue 2 is how would I go about retrieving previous messages. Messages are currently saved to MongoDB on the server. One approach could be to load the latest x amount of messages everytime a user clicks on a chat, however this means everytime a user selects a chat, they have to wait a small amount of time to retrieve the previous messages, even if no new messages have been added

An alternative to the issue above could be to initially load x amount of messages for all chats, therefore the user only fetches the previous messages (per chat) once, which is on mount. However, if new messages are added later on, the user possibly wouldn’t be able to see them, as clicking on the chat screen wouldn’t trigger a re-fetch of previous messages in this implementation.

Any suggestions? Thanks.

How to make Popover visible on top of all the parent components

My vague structure looks something like the following –

<div>
  <div> 
   <div class=PopoverNotOnTopOfThisDiv> //I want the popover to be on top of this Div
    <ul>
      <li></li>
      <li>
        <div>
        <div class=PopoverOnTopOfThisDiv> //Expands on click
           <div id=corousel>
             <ul class=corousel list>
                <div><li id=corousel item1> On Click opens a Popup</div>
                <div><li id=corousel item2></div>
             </ul>
           </div>
        </div>
      </li>
    </ul>
   </div>
  </div> 
</div>

Popover here is restricted within the div class=PopoverOnTopOfThisDiv but I need the popover to be visible on rest of the components as well.

Need help selecting specific parent box and select those children that belong to this children

Need help selecting specific parent box and select those children that belong to this children

    <ul class="categories-list">
      <li class="category"><input type="checkbox" class='parent' id='1' onclick="toggle(this)"><label>Parent category</label>
        <a class='collapse-category' href=''>expand</a>
        <ul class="subcategories">
          <li class="category"><input type="checkbox" class='eztext' id='1' value='Child categoryad'>Child categoryad</li>
          <li class="category"><input type="checkbox" class='eztext' id='1' value='Child category44 '>Child category44 </li>
          <li class="category"><input type="checkbox" class='eztext' id='1'>Child category2332 ></li>
          <li class="category"><input type="checkbox" class='eztext' id='1'>Child category878 </li>
        </ul>
      </li>
      <li class="category"><input type="checkbox" class='parent' id='2' onclick="toggle(this)"><label>Parent category2</label>
        <a class='collapse-category' href=''>expand</a>
        <ul class="subcategories">
          <li class="category"><input type="checkbox" class='eztext' id='2'>Child category2897</li>
          <li class="category"><input type="checkbox" class='eztext' id='2'>Child category26 </li>
          <li class="category"><input type="checkbox" class='eztext' id='2'>Child category2qs </li>
          <li class="category"><input type="checkbox" class='eztext' id='2'>Child category2,jhj </li>
        </ul>
      </li>
    </ul>
const checkboxes = document.querySelectorAll(".eztext");
const parent = document.querySelectorAll(".parent");

function toggle(itemId) {
    for (var i = 0; i < checkboxes.length; i++) {
        if (checkboxes[i] != itemId)
            checkboxes[i].checked = itemId.checked;
    }
}

Please help vanilla javascript only

Can’t center elements using React/JS/M

I can’t align my elements to which they are in the middle of my page. I want 3 elements per row, and n rows as needed depending on the number of “VideoBox”es. I need it to fit within the lettering of the P R O F E S S I O N A L P R O J E C T S title. (see image below)

I am not that great at JS/TS/CSS/React, so any advice will be helpful!

I have attached all the involved code.

/*VideoFile.tsx*/
import React from 'react';
import './VideoFile.css'


export default function VideoBox({ name, image, link, release, popularity }) {
  return (
    <div>
        <div className="box">
            <img src={image} alt={name} />
        </div>
        <div className ="img-name">{name}</div>
    </div>
  );
};

export { VideoBox };
/*VideoFile.css*/
  .box {
    position: relative;
    width: 200px;
    height: 200px;
    border: 1px solid white;
    overflow: hidden;
  }
  
  .box a {
    display: block;
    width: 100%;
    height: 100%;
    text-decoration: none; 
  }
  
  .box img {
    width: 100%;
    height: auto;
    object-fit: cover;
  }
  
  .img-name {
    color: white;
    font-weight: normal;
    margin-top: 2px; 
    margin-right:100px;
    font-family: 'Segoe UI';
    font-size: px;
    max-width: 207px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-bottom: 20px;
    position: relative;
    text-align: left;
  }
/*Professional Projects.js*/
import React from 'react';
import Box from '@mui/material/Box';

import Grid from '@mui/material/Grid';
import VideoBox from 'C:/Users/gavdog/VSC/ColtonWebsite/src/components/VideoFile.tsx';
import './Page.css';

export default function Home() {
  const videoFiles = [
    { name: 'Awesome Adventure', image: '/IMG_9676.jpg', link: '#', release: '2023-01-01', popularity: 80 },
    { name: 'Fantastic Journey', image: '/IMG_9676.jpg', link: '#', release: '2023-02-15', popularity: 92 },
    { name: 'Epic Exploration', image: '/IMG_9676.jpg', link: '#', release: '2023-03-21', popularity: 65 },
    { name: 'Amazing Discovery', image: '/IMG_9676.jpg', link: '#', release: '2023-04-10', popularity: 87 },
    { name: 'Cool Quest', image: '/IMG_9676.jpg', link: '#', release: '2023-05-05', popularity: 75 },
    { name: 'Unbelievable Voyage', image: '/IMG_9676.jpg', link: '#', release: '2023-06-18', popularity: 89 },
    { name: 'Incredible Expedition', image: '/IMG_9676.jpg', link: '#', release: '2023-07-30', popularity: 78 },
    { name: 'Exciting Trek', image: '/IMG_9676.jpg', link: '#', release: '2023-08-22', popularity: 94 },
    { name: 'Breathtaking Safari', image: '/IMG_9676.jpg', link: '#', release: '2023-09-14', popularity: 82 },
    { name: 'Adventurous Safari', image: '/IMG_9676.jpg', link: '#', release: '2023-10-09', popularity: 88 },
  ];

  

  return (
    <div className="gradient_background">
        <div className="title">Professional Work</div>
        <br />
        <br />
        <br />
        <Box sx={{
          position: 'absolute',
          top: '30%',
          mx: "auto"
        }}>
            <Grid container spacing={0} mx="auto" zIndex='tooltip'>
                {videoFiles.map((video, index) => (
                    <Grid key={index} xs={4}>
                        <VideoBox
                            name={video.name}
                            image={video.image}
                            link={video.link}
                            release={video.release}
                            popularity={video.popularity}
                        />
                    </Grid>
                ))}
            </Grid>
        </Box>
    </div>
  );
}
/*Page.css*/
.gradient_background {
    background: linear-gradient(90deg, #010101, #161616);
    height: 100vh;
    width: 99vw;
}
body {
    margin: 0;
    padding: 0;
}
.title {
    font-family: 'Harmony', harmony;
    font-size: 52px;
    letter-spacing: 12px;
    margin-bottom: 20px;
    color: #f3f1f1;
    text-align: center;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    padding-top: 7rem;
    padding-bottom: 0rem;
}
.trailer{
    font-family: 'Westmount', Westmount;
    font-size: 41px;
    letter-spacing: 2px;
    margin-bottom: 20px;
    color: #f3f1f1;
    position: relative;
    max-width: 700px;
    margin: 1px auto;
    text-align: center;
    padding-bottom: 6rem;
}

.trailer::before {
    content: " ";
    display: block;
    height: 1px;
    width: 130px;
    position: absolute;
    top: 18%;
    left: 0;
    background: #dbdbdb;
}
  
.trailer::after {
    content: " ";
    display: block;
    height: 1px;
    width: 130px;
    position: absolute;
    top: 18%;
    right: 0;
    background: #f3f1f1;
    
}

.sec{
    font-family: 'MagnumSans', magnumsans;
    font-size: 36px;
    letter-spacing: 7px;
    margin-bottom: 20px;
    color: #f7f4f4;
    text-align: center;
}
.wrapper {
    display: flex;
    justify-content: center; 
    align-items: center; 
    height: 86vh;
}
.center-content {
    text-align: center;
}

.box {
    border: 2px solid #fffefe;
    width: 450px; 
}

.box img {
    width: 100%;
    height: auto; 
    object-fit: fill;
    display: block;
}

.text-group {
    margin-left: 50px;
    padding-top: 1.4rem;
    font-family: 'MagnumSans', magnumsans;
    font-size: 24.6px;
    max-width: 700px;
    margin-bottom: 20px;
    color: #f3f1f1;
    position: relative;
    text-align: left;
}

.sec {
    text-align: center;
    color: white;
}

.link{
    color: #FFFFFF
}
.link:hover{
    color: #f5ebeb;
    transform: scale(1.2)
}

And here is an image of what it currently looks like

Ignore the 404s they are just place holder pictures

How can I add different infromation every time into different rows, with same id into mysql db [duplicate]

So one product can have one kind of specification

enter image description here

like height or width, but another one might not even need that if I am selling a PC and CAR every one of them has a different description. my question is how can I implement it in MySQL if reason*_*inner_id is how many fields I have up there in the photo it should be 1,2,3,4,5 reason, for example, is Size and content 29″x38″ how do input it every time with a different row like in this image

enter image description here in the form section I have a thing like that

enter image description here

I tried adding up every final information with javascript into the hidden field, cutting them, and then POST ing it into SQL but the problem always is that it’s not an array, and am stuck. I think JSON might help but I don’t know how to approach it

Get Spotify Token and display info in next.js

Simple Next.js component that displays currently playing song on spotify.

Context:
using app Router

since, to get token, spotify requires me to make a serverside call, Hence im making the complete request to fetch song from a api endpoint localhost/api/player

const getAccessToken = async () => {
  const client_id = 'key'; // Replace with your client ID
  const client_secret = 'key'; // Replace with your client secret

  
  fetch('https://accounts.spotify.com/api/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: `Basic ${btoa(`${client_id}:${client_secret}`)}`,
    },
    body: 'grant_type=client_credentials'
  })
    .then(response => response.json())
    .then(data => {
      console.log(data.access_token);
      return data.access_token;
    });
    return null;
}


export const fetchCurrentPlayingSong = async () => {
    const accessToken = await getAccessToken();
    const response = await fetch('https://api.spotify.com/v1/me/player/currently-playing', {
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    });
  
    if (!response.ok) {
      throw new Error(`Failed to fetch currently playing song: ${response.statusText}`);
    }
  
    const song = await response.json();
    return song;
  };

error: enter image description here

Unhandled Runtime Error

Error: The default export is not a React Component in page: “/api/player”

i tried to run this on clientside, but spotify doesnt allow fetching tokens on client side.
i just want to get serversideprops from sever about the currently playing song every 5 seconds
or the best way to display currently playing song

Please rate the code for the js shopping cart [closed]

This is my first time writing oop in js and I want to know how well or poorly the code is written and what I should fix. I wrote according to the MVC methodology.


import { items } from '../../items.js';
import { cartProducts } from '../cards.js';

class CardView {
   static cart = document.querySelector('.cart__content');
   static cartTitle = CardView.cart.querySelector('.cart__title-wrap');
   static cartPurchaseBlock = document.getElementById('cart_purchase');
   static cartTotalCost = document.getElementById('cart_total-cost');
   static cardBtnIcon = `<img src="./icons/buy.svg" alt="Purchase button">`;

   cartCard;

   constructor(cardElement) {
      this.cardElement = cardElement;
      this.addBtn = this.cardElement.querySelector('.coffee__buy');
      this.controller = new CardController(this.cardElement.dataset.productId);

      this.bindListeners();
   }

   createCartCard(productInfo) {
      return `<li class="cart__product" data-product-id="${productInfo.id}">
               <div class="cart__product-img-wrap">
                  <img src=${productInfo.img} />
               </div>
               <h3 class="cart__product-name">${productInfo.name}</h3>
               <div class="cart__product-change">
                  <button type="button" data-action="minus">-</button>
                  <p class="cart__product-amount">${productInfo.amount}</p>
                  <button type="button" data-action="plus">+</button>
               </div>
               <p class="cart__product-total-cost">${productInfo.totalCost} K</p>
            </li>`;
   }

   mountCartCard(product) {
      CardView.cartTitle.classList.add('cart__title-wrap--disappeared');
      CardView.cartPurchaseBlock.classList.add('cart__purchase--showed');

      if (!this.cartCard) {
         const cartItem = this.createCartCard(product);
         CardView.cart.insertAdjacentHTML('beforeend', cartItem);

         this.cartCard = CardView.cart.querySelector(`.cart__product[data-product-id="${product.id}"]`);

         const addBtnCart = this.cartCard.querySelector('[data-action=plus]');
         const removeBtnCart = this.cartCard.querySelector('[data-action=minus]');
         this.bindCartListeners(addBtnCart, removeBtnCart);
      }
   }

   unmountCartCard(product) {
      this.cartCard.remove();
      this.cartCard = null;

      if (!CardView.cart.contains(CardView.cart.querySelector('.cart__product'))) {
         CardView.cartTitle.classList.remove('cart__title-wrap--disappeared');
         CardView.cartPurchaseBlock.classList.remove('cart__purchase--showed');
      }
   }

   renderCartTotalCost(totalCost) {
      CardView.cartTotalCost.innerText = `${totalCost} K`
   }

   updateAmount(product) {
      this.onRenderCartTotalCost();

      if (product.amount > 0) {
         this.mountCartCard(product);
         this.addBtn.classList.add('coffee__buy--selected');
         this.addBtn.innerHTML = product.amount;

         this.cartCard.querySelector('.cart__product-amount').innerText = product.amount;
         this.cartCard.querySelector('.cart__product-total-cost').innerText = `${product.totalCost} K`;
      }
      else {
         this.unmountCartCard(product);
         this.addBtn.innerHTML = CardView.cardBtnIcon;
         this.addBtn.classList.remove('coffee__buy--selected');
      }
   }

   onAddToCart = () => {
      this.updateAmount(this.controller.handleAddToCart());
   }

   onRemoveFromCart = () => {
      this.updateAmount(this.controller.handleRemoveFromCart());
   }

   onRenderCartTotalCost = () => {
      this.renderCartTotalCost(this.controller.handleRenderCartTotalCost());
   }

   bindListeners() {
      this.addBtn.addEventListener('click', this.onAddToCart)
   }

   bindCartListeners(addBtnCart, removeBtnCart) {
      addBtnCart.addEventListener('click', this.onAddToCart);
      removeBtnCart.addEventListener('click', this.onRemoveFromCart);
   }

}

class CardController {
   constructor(productId) {
      this.model = new CardModel(+productId, cartProducts);
   }

   handleAddToCart() {
      return this.model.addToCart();
   }

   handleRemoveFromCart() {
      return this.model.removeFromCart();
   }

   handleRenderCartTotalCost() {
      return this.model.getCartTotalCost();
   }

}

class CardModel {
   products = items;
   cartProducts = cartProducts;

   constructor(productId) {
      this.product = this.products.find(product => product.id === productId);
      this.productId = productId;
   }

   getCartProductIndex() {
      return this.cartProducts.findIndex(cartProduct => cartProduct.id === this.productId);
   }

   addToCart() {
      const cartProductIndex = this.getCartProductIndex();
      let cartProduct;

      if (cartProductIndex === -1) {
         cartProduct = {
            ...this.product,
            amount: 1,
            get totalCost() {
               return this.cost * this.amount;
            }
         };

         this.cartProducts.push(cartProduct);
      }
      else {
         cartProduct = this.cartProducts[cartProductIndex];
         ++cartProduct.amount;
      }

      return cartProduct;
   }

   removeFromCart() {
      const cartProductIndex = this.getCartProductIndex();
      const cartProduct = cartProducts[cartProductIndex];

      cartProduct.amount--;

      if (cartProduct.amount === 0) {
         this.cartProducts.splice(cartProductIndex, 1);
      }

      return cartProduct;
   }

   getCartTotalCost() {
      return this.cartProducts.reduce((cartTotalCost, cartProduct) => cartTotalCost + cartProduct.totalCost, 0);
   }
}

export { CardView, CardController, CardModel };

The final file with the cart functionality came out to 180 lines, but I’m not sure about the correctness of the writing and effectiveness of the code.

How can I display the messages returned by the POST function on a web page

I want to do the following functionality. On the index.ejs page I click the SYNC button and in <span> {{ responsePost }} </span> get a sequence of messages from the runSync function

<button id="msk" class="btn btn-primary" type="button" @click="runSync">SYNC</button>
<span> {{ responseRunSync }} </span>

After clicking the SYNC button, a Vue method is executed that calls the express API:

methods: {
  runSync() {
    const options = {
      method: 'POST',
      redirect: 'follow',
    }
    fetch('/api/sync/', options)
  },
},

It is express

import { runSync } from './cucm-ldap-sync-fetch.js'

app.post('/api/sync', (req, res) => {
  res.render('index', {
    sync: runSync(0),
  })
})

The runSync function calls the Cisco PBX API and:

  1. starts synchronizing with MS Active Directory and returns the API message
    'Sync initiated successfully'
  2. checks the status of the process and returns the API message
    'Sync is currently under process'
  3. setInterval runs ldapStatus every 2 seconds and when the
    'Sync is performed successfully'
    message is received from the API, the function ends
const runSync = async (item) => {

  const ldapSync = async () => {
    try {
      const response = await fetch(url, reqSyncOptions)
      const XMLdata = await response.text()
      const parser = new XMLParser()
      const jObj = parser.parse(XMLdata)
      console.log(ucmData[item].title)
        console.log(jObj)
        return jObj
  }

  const ldapStatus = async () => {
    try {
      const response = await fetch(url, reqStatusOptions)
      const XMLdata = await response.text()
      const parser = new XMLParser()
      const jObj = parser.parse(XMLdata)
        if (jObj === 'Sync is performed successfully') {
          clearInterval(id)
        }
        console.log(text)
        return text
  }

  const id = ldpSync()

  function ldpSync() {
    ldapSync()
    const idInterval = setInterval(() => {
      ldapStatus()
    }, 2000)
    return idInterval
  }
}

export { runSync }

Example API messages:

Sync initiated successfully
Sync is currently under process
Sync is currently under process
Sync is performed successfully

How can I output these messages to the index.ejs page? As a result, all messages should be visible in the order they were received.

Chart.js Line chart changes its y axis dynamically

I got this chart

I am implementing admin dashboard that will show information about CPU usage

So i need to show range of values on y axis from 0-100 (in percentage), but when i try to implement this, chart dynamically changes y axis values instead of being from 0-100 static

javascript

new Chart(document.querySelector('#lineChart'), {
          type: 'line',
          data: {
            labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
            datasets: [{
              label: 'Line Chart',
              data: [20, 30, 80, 15, 5, 4, 55],
              fill: true,
              borderColor: 'rgb(75, 192, 192)',
              tension: 0.1
            }]
          },
          options: {
            scales: {
              y: {
                type: 'linear',
                ticks: {
                  beginAtZero: true,
                  stepSize: 20, // Customize the step size if needed
                  min: 0,
                  max:100,
                  suggestedMax: Math.max(maxValue,100), // Set the maximum value of the y-axis to 100
                },
              }
            }
          }
        });

html

          <canvas id="lineChart" style="max-height: 400px; display: block; box-sizing: border-box; height: 234px; width: 469px;" width="469" height="234"></canvas>


How can I get my time to stop so that my sprites can’t attack eachother after the time is up?

I am currently creating a fighting game, and I have two sprites (the player is blue and the enemy is red). I currently have a working timer. However, after the time is up, the sprites can still attack each other and the text in the middle will change constantly (it will make more sense if you run the snippet).

Question: How can I make it so that my sprites cannot attack each other after the timer is finished?

Side question: How can I also stop my sprites from moving after the timer runs out? I feel like if the first on gets answered, I can do this question

Note: I am not using any libraries or frameworks, I am using native JavaScript, CSS, and HTML.

Note: Using “enter” or “space”, make when of the sprites get down to no health. Then with the other sprite, smack that original one a bunch more. Eventually, the text will switch from “Player 1/2 wins!” to “Player 2/1 wins!”, even though the health is already gone.

const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext("2d");

const winnerDisplay = document.querySelector('#winner-display');

canvas.width = 1024;
canvas.height = 576;

function logMessage(msg) {
    document.querySelector('#myMessage').textContent += msg + '. ';
}

ctx.fillRect(0, 0, canvas.width, canvas.height);

const gravity = 0.7;

class Sprite {
    constructor({position, velocity, color, offset}) {
        this.position = position;
        this.velocity = velocity;
        this.height = 150;
        this.width = 50;
        this.lastKey;
        this.attackBox = {
            position: {
                x: this.position.x,
                y: this.position.y
            },
            offset,
            width: 100,
            height: 50,
            color: 'green'
        }
        this.color = color;
        this.isAttacking;
        this.health = 100;
    }

    draw() {
        ctx.fillStyle = this.color;
        ctx.fillRect(this.position.x, this.position.y, this.width, this.height);

        //Attack box
        if (this.isAttacking) {
            ctx.fillStyle = this.attackBox.color;
            ctx.fillRect(
                this.attackBox.position.x,
                this.attackBox.position.y,
                this.attackBox.width,
                this.attackBox.height
            )
        }
    }

    update() {
        this.draw();
        this.attackBox.position.x = this.position.x - this.attackBox.offset.x;
        this.attackBox.position.y = this.position.y;
        this.position.x = Math.max(0, Math.min(canvas.width - this.width, this.position.x + this.velocity.x));
        this.position.y = Math.max(0, Math.min(canvas.height, this.position.y + this.velocity.y));

        if (this.position.y + this.height + this.velocity.y >= canvas.height) {
            this.velocity.y = 0;
        } else {
            this.velocity.y += gravity;
        }
    }

    attack() {
        this.isAttacking = true;
        
        setTimeout(() => {
            this.isAttacking = false
        }, 50)
    }
}

const Player = new Sprite({
    position: {
        x: 0,
        y: 0
    },
    velocity: {
        x: 0,
        y: 0
    },
    offset: {
        x: 0,
        y: 0
    },
    color: 'blue'
})

const Enemy = new Sprite({
    position: {
        x: canvas.width,
        y: 100
    },
    velocity: {
        x: 0,
        y: 0
    },
    offset: {
        x: 50,
        y: 0
    },
    color: 'red'
})

const keys = {
    w: {
        pressed: false
    },
    a: {
        pressed: false
    },
    d: {
        pressed: false
    },
    ArrowUp: {
        pressed: false
    },
    ArrowLeft: {
        pressed: false
    },
    ArrowRight: {
        pressed: false
    }
}

function rectangularCollision({rectangle1, rectangle2}) {
    return (
        rectangle1.attackBox.position.x + rectangle1.attackBox.width >= rectangle2.position.x &&
        rectangle1.attackBox.position.x <= rectangle2.position.x + rectangle2.width &&
        rectangle1.attackBox.position.y + rectangle1.attackBox.height >= rectangle2.position.y &&
        rectangle1.attackBox.position.y <= rectangle2.position.y + rectangle2.height &&
        rectangle1.isAttacking
    )
}

function determineWinner({ Player, Enemy, timerId }) {
    clearTimeout(timerId);
    winnerDisplay.style.display = 'flex';

    if (Player.health == Enemy.health) {
        winnerDisplay.innerHTML = 'Tie!';
    }

    if (Player.health > Enemy.health) {
        winnerDisplay.innerHTML = 'Player 1 wins!';
    }

    if (Player.health < Enemy.health) {
        winnerDisplay.innerHTML = 'Player 2 wins!';
    }
}

//Timer and end game
//Feel free to change these starting values as you need
const startingMinutes = 1;
const startingSeconds = 30;
let time = startingMinutes * 60 + startingSeconds;
let timerId;

function decreaseTimer() {
    const timeLeft = document.querySelector('#time-left');

    if (time > 0) {
        timerId = setTimeout(decreaseTimer, 1000);

        const minutes = Math.floor(time / 60);
        let seconds = time % 60;

        seconds = seconds < 10 ? '0' + seconds : seconds;
        timeLeft.innerHTML = `0${minutes}:${seconds}`;
        time--;
    }

    if (time == 0) {
        timeLeft.innerHTML = `00:00`;
        determineWinner({ Player, Enemy, timerId });

        Player.position.x = 0;
        Enemy.position.x = canvas.width;
    }    
}

decreaseTimer();

function animate() {
    window.requestAnimationFrame(animate);
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    Player.update();
    Enemy.update();

    //Player movement
    Player.velocity.x = 0;

    if (keys.a.pressed == true && Player.lastKey == "a") {
        Player.velocity.x = -5;
    } else if (keys.d.pressed == true && Player.lastKey == "d") {
        Player.velocity.x = 5;
    }

    //Enemy movement
    Enemy.velocity.x = 0;

    if (keys.ArrowLeft.pressed == true && Enemy.lastKey == "ArrowLeft") {
        Enemy.velocity.x = -5;
    } else if (keys.ArrowRight.pressed == true && Enemy.lastKey == "ArrowRight") {
        Enemy.velocity.x = 5;
    }

    //Detect for the player hitting the enemy
    if (rectangularCollision({rectangle1: Player, rectangle2: Enemy})) {
        Player.isAttacking = false;
        Enemy.health -= 20;
        document.querySelector('#enemy-health').style.width = Enemy.health + '%';
    } 
    
    //Detect for the enemy hitting the player
    if (rectangularCollision({rectangle1: Enemy, rectangle2: Player})) {
        Enemy.isAttacking = false;
        Player.health -= 20;
        document.querySelector('#player-health').style.width = Player.health + '%';
    }  
    
    //End game based on health
    if (Enemy.health <= 0 || Player.health <= 0) {
        determineWinner({ Player, Enemy, timerId });
    }
}

animate();

window.addEventListener('keydown', (event) => {
    switch (event.key) {
        //Player movement
        case 'w':
            Player.velocity.y = -20;
        break;

        case 'a':
            keys.a.pressed = true;
            Player.lastKey = "a";
        break

        case 'd':
            keys.d.pressed = true;
            Player.lastKey = "d";
        break;

        case ' ':
            Player.attack();
        break;    
    }

    //Enemy movement
    switch (event.key) {
        case 'ArrowUp':
            Enemy.velocity.y = -20;
        break;

        case 'ArrowRight':
            keys.ArrowRight.pressed = true;
            Enemy.lastKey = 'ArrowRight'
        break

        case 'ArrowLeft':
            keys.ArrowLeft.pressed = true;
            Enemy.lastKey = 'ArrowLeft'
        break;

        case 'Enter':
            Enemy.attack();
        break;    
    }    
})

window.addEventListener('keyup', (event) => {
    //Player keys
    switch (event.key) {
        case 'w':
            keys.w.pressed = false;
        break;    

        case 'a':
            keys.a.pressed = false;
        break

        case 'd':
            keys.d.pressed = false;
        break;
    }

    //Enemy keys
    switch (event.key) {
        case 'ArrowUp':
            keys.ArrowUp.pressed = false;
        break;    

        case 'ArrowLeft':
            keys.ArrowLeft.pressed = false;
        break

        case 'ArrowRight':
            keys.ArrowRight.pressed = false;
        break;
    }
})
* {
    box-sizing: border-box;
}

#mother-div {
    position: relative;
    display: inline-block;
}

#small-container {
    position: absolute;
    display: flex;
    width: 100%;
    align-items: center;
    padding: 20px;
}

.healthDiv {
    position: relative;
    height: 30px;
    width: 100%;
}

.backgroundHealth {
    background-color: red;
    height: 30px;
    width: 100%
}

#player-health {
    position: absolute;
    background-color: lime;
    top: 0;
    right: 0;
    bottom: 0;
    width: 100%
}

#enemy-health {
    position: absolute;
    background-color: yellow;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0
}

#playerHealthDiv {
    display: flex;
    justify-content: flex-end;
}

#timer {
    background-color: red;
    width: 100px;
    height: 100px;
    flex-shrink: 0;
    border: solid white 2px;
    display: flex;
    align-items: center;
    justify-content: center;
}

#winner-display {
    position: absolute;
    color: white;
    align-items: center;
    justify-content: center;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    display: none;
}

#myConsole {
    background-color: black;
    color: white;
    min-height: 100px;
}
<!DOCTYPE html>
<html lang="en">

<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE-edge">
        <meta name="viewport", content="width=device-width, initial-scale=1.0">
        <meta name="author" content="Christian Davis">
        <link rel="stylesheet" href="styles.css">

        <title>Fighting Game</title>
    </head>

    <body>
        <!--Red container div-->
        <div id="mother-div">
            <!--Smaller red container div-->
            <div id="small-container">
                <!--Player Health-->
                <div class="healthDiv" id="playerHealthDiv">
                    <div class="backgroundHealth"></div>
                    <div id="player-health"></div>
                </div>

                <!--Timer-->
                <div id="timer">
                    <span id="time-left">02:00</span>
                </div>

                <!--Enemy Health-->
                <div class="healthDiv">
                    <div class="backgroundHealth"></div>
                    <div id="enemy-health"></div>
                </div>
            </div>

            <!--Winner display-->
            <div id="winner-display"></div>

            <canvas id="canvas"></canvas>
        </div>

        <p id="myConsole">&gt;&nbsp;<span id="myMessage"></span></p>

        <script src="app.js"></script>
    </body>
</html>

Error 403 Spotify API : ERR_ABORTED 403 (Forbidden)

I am getting frustrated with this error :
main.js:86 GET https://api.spotify.com/v1/me/top/artists net::ERR_ABORTED 403 (Forbidden).

My JS code looks like this:

function getTopArtists(accessToken) {
  return fetch("https://api.spotify.com/v1/me/top/artists", {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });
}
async function displayTopArtistsInConsole(profile) {
  const accessToken = ACCESS_TOKEN;

  getTopArtists(accessToken)
    .then((response) => {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error("Failed to fetch top artists");
      }
    })
    .then((data) => {
      console.log("Top Artists:", data.items);
    })
    .catch((error) => {
      console.error(error);
    });
}

I have tried debugging my code but I really cannot find any solution to my problem, neither by reading all the documentation on Spotify API. I am sure that my token is not expired at all because it can last up to 1 hour and I am running the code at the exact moment I open my HTML page. Could it have to do with the async keyword or something? I can provide the full code here thanks in advance :

const clientId = "50a6862feb5a4139af099a0ca99e267a";
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
let ACCESS_TOKEN = null;
if (!code) {
  document.getElementById("sign-in").addEventListener("click", () => {
    redirectToAuthCodeFlow(clientId);
  });
} else {
  const accessToken = await getAccessToken(clientId, code).then();
  // fetch("https://api.spotify.com/v1/me/top/tracks").then((res) => console.log(res));
  const profile = await fetchProfile(accessToken);
  populateUI(profile);
}

function generateCodeVerifier(length) {
  let text = "";
  let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for (let i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

async function generateCodeChallenge(codeVerifier) {
  const data = new TextEncoder().encode(codeVerifier);
  const digest = await window.crypto.subtle.digest("SHA-256", data);
  return btoa(String.fromCharCode.apply(null, [...new Uint8Array(digest)]))
    .replace(/+/g, "-")
    .replace(///g, "_")
    .replace(/=+$/, "");
}

export async function redirectToAuthCodeFlow(clientId) {
  const verifier = generateCodeVerifier(128);
  const challenge = await generateCodeChallenge(verifier);

  localStorage.setItem("verifier", verifier);

  const params = new URLSearchParams();
  params.append("client_id", clientId);
  params.append("response_type", "code");
  params.append("redirect_uri", "http://localhost:5173/callback");
  params.append("scope", "user-read-private user-read-email");
  params.append("code_challenge_method", "S256");
  params.append("code_challenge", challenge);

  document.location = `https://accounts.spotify.com/authorize?${params.toString()}`;
}

export async function getAccessToken(clientId, code) {
  const verifier = localStorage.getItem("verifier");

  const params = new URLSearchParams();
  params.append("client_id", clientId);
  params.append("grant_type", "authorization_code");
  params.append("code", code);
  params.append("redirect_uri", "http://localhost:5173/callback");
  params.append("code_verifier", verifier);

  const result = await fetch("https://accounts.spotify.com/api/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: params,
  });

  const { access_token } = await result.json();

  ACCESS_TOKEN = access_token;
  return access_token;
}

async function fetchProfile(token) {
  const result = await fetch("https://api.spotify.com/v1/me", {
    method: "GET",
    headers: { Authorization: `Bearer ${token}` },
  });

  return await result.json();
}

function getTopArtists(accessToken) {
  return fetch("https://api.spotify.com/v1/me/top/artists", {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });
}
async function displayTopArtistsInConsole(profile) {
  const accessToken = ACCESS_TOKEN;

  getTopArtists(accessToken)
    .then((response) => {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error("Failed to fetch top artists");
      }
    })
    .then((data) => {
      console.log("Top Artists:", data.items);
    })
    .catch((error) => {
      console.error(error);
    });
}

function populateUI(profile) {
  console.log(profile);

  const profileSection = document.createElement("section");
  profileSection.setAttribute("id", "profile");

  const h2 = document.createElement("h2");
  h2.innerHTML = 'Il tuo profilo <span id="displayName"></span>';
  profileSection.appendChild(h2);

  const avatarSpan = document.createElement("span");
  avatarSpan.setAttribute("id", "avatar");
  profileSection.appendChild(avatarSpan);

  const ul = document.createElement("ul");
  ul.id = "lista";

  const profileDataElements = [
    { label: "User ID", id: "id" },
    { label: "Email", id: "email" },
    { label: "Spotify URI", id: "uri", isLink: true },
    { label: "Link", id: "url", isLink: true },
    { label: "Profile Image", id: "imgUrl" },
  ];

  profileDataElements.forEach((data) => {
    const li = document.createElement("li");
    const span = document.createElement("span");
    span.setAttribute("id", data.id);
    li.textContent = `${data.label}: `;

    if (data.isLink) {
      const link = document.createElement("a");
      link.setAttribute("id", data.id);
      link.setAttribute("href", "#");
      li.appendChild(link);
    } else {
      li.appendChild(span);
    }

    ul.appendChild(li);
  });

  profileSection.appendChild(ul);

  document.body.appendChild(profileSection);

  document.getElementById("displayName").innerText = profile.display_name;
  if (profile.images[0]) {
    const profileImage = new Image(200, 200);
    profileImage.src = profile.images[0].url;
    document.getElementById("avatar").appendChild(profileImage);
    document.getElementById("imgUrl").innerText = profile.images[0].url;
  }
  document.getElementById("id").innerText = profile.id;
  document.getElementById("email").innerText = profile.email;
  document.getElementById("uri").innerText = profile.uri;
  document.getElementById("uri").setAttribute("href", profile.external_urls.spotify);
  document.getElementById("url").innerText = profile.href;
  document.getElementById("url").setAttribute("href", profile.href);

  document.getElementById("profile").style.display = "block";

  document.getElementById("nota").remove();
  document.getElementById("nota").remove();
  document.getElementById("sign-in").remove();
  displayTopArtistsInConsole(profile);
}

document.addEventListener("DOMContentLoaded", async function () {
  const params = new URLSearchParams(window.location.search);
  const code = params.get("code");

  if (code) {
    const accessToken = await getAccessToken(clientId, code);
    const profile = await fetchProfile(accessToken);
    populateUI(profile);
  }
});

Setting user’s uid as the document’s identifier in my users collection in cloud firestore [duplicate]

I am creating a sign up module in react native expo where I am using firebase’s email/password authentication. To store the additional signup fields, I am using cloud firestore as my database.

Now inside my users collection, I want to add each record by having the uid inside the record as the document’s title for facilitated lookups later.

try {
      const userCredential = await createUserWithEmailAndPassword(auth, email, password);
      const user = userCredential.user;
  
      await AsyncStorage.setItem('userData', JSON.stringify(user));

      try {
        const docRef = await addDoc(collection(db, "users", user.uid), {
          email: email,
          username: username,
          phonenumber: phonenumber,
          uid: user.uid,
          isAdmin: false,
          isSeller: false,
          sellerID: null,
          chats: [],
        });
        console.log("Document written with ID: ", docRef.id);
      } catch (e) {
        console.error("Error adding document: ", e);
      }

So i tried adding my user document like above in my users collection but firebase throws the following error upon execution.

ERROR Error adding document: [FirebaseError: Invalid collection reference. Collection references must have an odd number of segments, but users/RF1qXof3iIeDN9m28YutGa4486Y2 has 2.]

I was expecting this to set the document’s title equal to the uid but instead this error occurs.

Fixed calendar height in air-datepicker

I’m using the air-datepicker plugin. When switching between months or years, the height of the drop-down block with the calendar jumps, since there is an unequal number of days in the grid of different months. If the position of the container is under the input, then this does not raise any questions, but if it is above the input, then the change in height looks very bad.

How to solve a problem?

There used to be a fixedHeight option, but it’s gone now.

documentation Help for the JS function by the number of parameters entered by the user

I have a function in Google Script, inside the function is defined an option to insert one argument, or three arguments.
How can I add documentation to a Google Sheets user to show them how to use the one argument option, or how to use the three argument option?

This is my code:

var GDate = function (HDate) {
    if (arguments.length == 1) arguments = arguments[0].split(' ');

    var D = letterToNum(arguments[0]);
    var M = monthMap[findClosest(Array.from(arguments).slice(1, arguments.length - 1).join(' '), monthMap)];
    var Y = letterToNum(arguments[arguments.length - 1], 1);

    var G = HG(Y);

    var days = G.daysmonth.slice(0, G.monthsYear.indexOf(M)).reduce((total, value) => total + value, 0);
    G.Date.setDate(G.Date.getDate() + days + D);
    return G.Date;
}

I want to add documentation conditional on the number of parameters the user will enter,
This is the documentation I tried to write:

/**
      * This function calculates the corresponding Gregorian date from a given Hebrew date.
      * The function can accept the Hebrew date as a single string or as separate strings for day, month and year.
      *
      * option 1:
      * @type {string} HDdate - a single string of the Hebrew date.
      * @param {A Tishrei 3737} HDdate - a single Hebrew date string in the "day month year" format.
      *
      * Option 2:
      * @type {string[D, M, Y]} HDdate - an array of strings representing the day, month and year in Hebrew.
      * @param {string} D - day of the Hebrew month.
      * @param {string} M - Hebrew month.
      * @param {string} Y - Hebrew year.
      *
      * @returns {Date} the corresponding Gregorian date.
      *@customfunction
      */

The documentation only shows the last HDdate option because only it appears in the function’s parentheses,
If I write the function like this:
var GDate = function (HDate, [D], [M], [Y])
When the user selects the string[] option, when he types D – HDdate is highlighted, when he types M he will see D highlighted, and when he types Y he will see M highlighted.

There is an example in Google Script, if I know how to write it, it will probably solve all my problems…
For example, when I use the getRange() function,
I see 4 different options how to enter parameters,
1/4 documentation for the getRange function in Google Script
2/4 documentation for the getRange function in Google Script
3/4 documentation for the getRange function in Google Script
4/4 documentation for the getRange function in Google Script

how can i merge multiple objects in an array (key value pairs)?

I’m trying to pass a key-value pair from node js to an EJS file.
using Mongoose for data retrieval
It’s not working.

User.find({}).select('name -_id').select('username').then(
        users =>{
            let JSONobject = {};

            for(var i = 0; i < users.length; i++){
                JSONobject = Object.assign(JSONobject, users[i]);
            }
            console.log(JSON.stringify(JSONobject));
            res.render("ToDo.ejs",{UserLIst: JSONobject.username});
        }
      )

above is the code that I used

getting something like this:

console pic

I was expecting something like this

[
  { username: '[email protected]',
    username: '[email protected]',
    username: '[email protected]' }
]