Angular 17 Array push Inserting null object to Array List

I am having a service that insert data in an array ( list ). service.ts code is below.
Problem: Data gets passed to service from component but after push to array it becomes null

import { Injectable } from '@angular/core';
import { User } from '../models/user.model';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private users: User[] = [
    { id: 1, name: 'John Doe', email: '[email protected]', phone: '123-456-7890' },
    { id: 2, name: 'Jane Smith', email: '[email protected]', phone: '987-654-3210' }
  ];

 

  addUser(user: User): Observable<User> {
    user.id = this.users.length + 1;

    this.users.push(user);
    console.log("User Object-");
    console.log(user);
    console.log("Users List-");
    console.log(this.users);
    return of(user);
    //[![Console log results screenshot is added with question][1]][1]
  }
     
}

Hearts shown when clicking the show Love button [closed]

I have created a mother’s day web site that show loves when a show love button is clicked but when a Show Love button is clicked nothing happens the JS run into an error. I did not know whether it is because of the error that I did not see in my HTML, CSS or JavaScript.

In This CSS code I tried to change the font-family from Arial,sens-serif to symbol and results are the same.

this is a screenshort of how the web looks like

/* script.js */
document.getElementById("btn").addEventListener("click", function() {
  const heartsContainer = document.getElementById("hearts");
  for (let i = 0; i < 10; i++) {
    const heart = document.createElement("span");
    heart.textContent = "";

    heart.classList.add("heart");
    heartsContainer.apendchild(heart);
  }
});
body {
  font-family: Arial, sens-serif;
  background-color: #f2f2f2;
}

h1 {
  text-decoration: underline;
}

.container {
  width: 80%;
  margin: 40px auto;
  text-align: center;
}

#heading {
  font-size: 36px;
  color: #ff69b4;
}

#massage {
  font-size: 18px;
  color: #666;
}

#btn {
  background-color: #ff69b4;
  color: #fff;
  border: none;
  padding: 10px 20px;
  cursor: : pointer;
}

#hearts {
  margin-top: 20px;
}

.hearts {
  font-family: symbola;
  display: inline-block;
  font-size: 24px;
  color: #ff69b4;
  margin: 0 10px;
  animation: beat 1s infinite;
  content: #2665;
}

@keyframes beat {
  0% {
    transform: scale(1);
  }

  50% {
    transform: scale(1.2);
  }

  100% {
    transform: scale(1);
  }
}
<div class="container">
  <h1 id="heading">Happy Mother's Day!</h1>
  <p id="massage">To the most selfless and loving mom in the world...</p>
  <button id="btn">Show Love</button>
  <div id="hearts"></div>
</div>
<script type="text/javascript" src="script.js"></script>

auto-fill form fields from URL parameters in wix

import wixLocation from 'wix-location';

$w.onReady(function () {
    let query = wixLocation.query['email'];
    let email = decodeURIComponent(query); //decode uri encoded values in readable format
    console.log(email); //this is your email
});

auto-fill wix form fields from URL parameters in

Pointer Move Event always fires in React and fires only a few times in Vue

I am converting a React component to a Vue component and there is something I cannot figure out, and that is why the pointermove event fires in React but in Vue it fires at most 20 times and stops.

I have a runnable example here and I have two projects set up there are 100% the same, one with Vite/React and one with Vite/Vue. The CSS is the same for both, and they both use touch-action: none the same.

In the console when the device mode is mobile, the React component always logs ‘pointer move’, and in the Vue component it logs ‘pointer move’ a few times and stops.

If the device mode is desktop, the pointer move event fires the same for both Vue and React.

Vue Component

<script setup>
import { Teleport, ref, onMounted } from 'vue'
import './Drawer/style.css'
</script>

<template>
  <div>
    <Teleport to="body">
      <div>
        <div className="fixed inset-0 bg-black/40" />
        <div
          data-vaul-drawer
          <!-- fires only a few times -->
          @pointermove="() => console.log('pointer move')"
          className="bg-gray-100 flex flex-col rounded-t-[10px] mt-24 h-[80%] lg:h-[320px] fixed bottom-0 left-0 right-0 outline-none"
        >
          <div className="p-4 bg-white rounded-t-[10px] flex-1 overflow-y-auto">
            <div className="max-w-md mx-auto space-y-4">
              <div aria-hidden className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-gray-300 mb-8" />
              <div className="font-medium mb-4 text-gray-900">Ira Glass on Taste</div>
              <p className="text-gray-600">
                Nobody tells this to people who are beginners, I wish someone told me. All of us who do creative work,
                we get into it because we have good taste.
              </p>
              <p className="text-gray-600">
                But there is this gap. For the first couple years you make stuff, it’s just not that good. It’s trying
                to be good, it has potential, but it’s not. But your taste, the thing that got you into the game, is
                still killer. And your taste is why your work disappoints you. A lot of people never get past this
                phase, they quit.{' '}
              </p>
              <p className="text-gray-600">
                Most people I know who do interesting, creative work went through years of this. We know our work
                doesn’t have this special thing that we want it to have. We all go through this. And if you are just
                starting out or you are still in this phase, you gotta know its normal and the most important thing you
                can do is do a lot of work
              </p>
              <p className="text-gray-600">
                Put yourself on a deadline so that every week you will finish one story. It is only by going through a
                volume of work that you will close that gap, and your work will be as good as your ambitions. And I took
                longer to figure out how to do this than anyone I’ve ever met. It’s gonna take awhile. It’s normal to
                take awhile. You’ve just gotta fight your way through.
              </p>
            </div>
          </div>
        </div>
      </div>
    </Teleport>
  </div>
</template>

React Component

import './Drawer/style.css'
import { createPortal } from 'react-dom'

export default function TestDrawer() {
  return (
    <div>
      {createPortal(
        <div>
          <div className="fixed inset-0 bg-black/40" />
          <div
            data-vaul-drawer
            {/* always fires */}
            onPointerMove={() => console.log('pointer move')}
            className="bg-gray-100 flex flex-col rounded-t-[10px] mt-24 h-[80%] lg:h-[320px] fixed bottom-0 left-0 right-0 outline-none"
          >
            <div className="p-4 bg-white rounded-t-[10px] flex-1 overflow-y-auto">
              <div className="max-w-md mx-auto space-y-4">
                <div aria-hidden className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-gray-300 mb-8" />
                <div className="font-medium mb-4 text-gray-900">Ira Glass on Taste</div>
                  <p className="text-gray-600">
                    Nobody tells this to people who are beginners, I wish someone told me. All of us who do creative work,
                    we get into it because we have good taste.
                  </p>
                  <p className="text-gray-600">
                    But there is this gap. For the first couple years you make stuff, it’s just not that good. It’s trying
                    to be good, it has potential, but it’s not. But your taste, the thing that got you into the game, is
                    still killer. And your taste is why your work disappoints you. A lot of people never get past this
                    phase, they quit.{' '}
                  </p>
                  <p className="text-gray-600">
                    Most people I know who do interesting, creative work went through years of this. We know our work
                    doesn’t have this special thing that we want it to have. We all go through this. And if you are just
                    starting out or you are still in this phase, you gotta know its normal and the most important thing you
                    can do is do a lot of work
                  </p>
                  <p className="text-gray-600">
                    Put yourself on a deadline so that every week you will finish one story. It is only by going through a
                    volume of work that you will close that gap, and your work will be as good as your ambitions. And I took
                    longer to figure out how to do this than anyone I’ve ever met. It’s gonna take awhile. It’s normal to
                    take awhile. You’ve just gotta fight your way through.
                  </p>
              </div>
            </div>
          </div>
        </div>,
        document.body
    )}
    </div>
  );
}

How to save Events that last multiple days in a database using FullCalendar

I have a calendar and different types of events. I can drag and drop these events into my calendar. Now I want to resize the events onto multiple days, then let go and automatically save the start and end of the event into my database. I can’t get it working properly, how I want it.

This are my tables, i use SQLite3:


CREATE TABLE "event_entwicklung" (
    "id"    INTEGER,
    "title" TEXT NOT NULL,
    "start" TEXT NOT NULL,
    "end"   TEXT,
    "description"   TEXT,
    "background_color"  TEXT,
    PRIMARY KEY("id" AUTOINCREMENT)
);

This is the PHP file where my calendar is loaded:

<?php
    session_start();
    include_once "../../../Backend/database.php";
    include_once "lb_kalender.php";

    $db = new Database();
?>

<!DOCTYPE html>
<html lang='en'>
<body data-username="<?php echo htmlspecialchars($_SESSION['username']); ?>">
    <div class="background-blur">
        <div id="calendar-container">
            <div id="calendar"></div>
        </div>
        <div class='Kalender_übersicht_links'>
            <h3>Ereignis-Liste</h3>
            <ul id="external-events" class="event-list">
            </ul>
        </div>

</body>
</html>

This is the JS file:

document.addEventListener('DOMContentLoaded', function() {
    var username = document.body.dataset.username;
    var calendarEl = document.getElementById('calendar');
    var calendarContainer = document.getElementById('calendar-container');

    function resizeCalendar() {
        const calendarContainer = document.getElementById('calendar-container');
        const leftDiv = document.querySelector('.kalender_übersicht_links');
        const topDiv = document.querySelector('.kalender_übersicht_oben');

        const leftWidth = leftDiv ? leftDiv.offsetWidth : 0;
        const topHeight = topDiv ? topDiv.offsetHeight : 0;

        calendarContainer.style.width = `${window.innerWidth - leftWidth}px`;
        calendarContainer.style.height = `${window.innerHeight - topHeight}px`;
        calendarContainer.style.left = `${leftWidth}px`;
        calendarContainer.style.top = `${topHeight}px`;
    }

    window.addEventListener('resize', resizeCalendar);
    resizeCalendar();

    function getCalendarType() {
        const path = window.location.pathname;
        if (path.includes('kalender_entwicklung')) return 'entwicklung';
        if (path.includes('kalender_support')) return 'support';
        if (path.includes('kalender_gesamt')) return 'gesamt';
        return 'entwicklung';
    }
    const calendarType = getCalendarType();

    var calendar = new FullCalendar.Calendar(calendarEl, {
        locale: 'de',
        themeSystem: 'bootstrap',
        droppable: true,
        selectable: true,
        editable: true,
        eventResizableFromStart: true,
        navLinks: true,
        weekNumbers: true,
        firstDay: 1,
        headerToolbar: {
            left: 'prev,next today',
            center: 'title',
            right: 'multiMonthYear,dayGridMonth'
        },
        views: {
            multiMonthYear: {
                type: 'multiMonth',
                duration: { years: 1 },
                buttonText: 'Year'
            },
            dayGridMonth: {
                buttonText: 'Month'
            },
        },
        viewDidMount: function(view) {
            if (view.view.type === 'multiMonthYear') {
                calendarContainer.style.width = '90%';
                calendarContainer.style.height = '90%';
            } else {
                calendarContainer.style.width = '90%';
                calendarContainer.style.height = '90%';
            }
        },
        eventClick: function(info) {
            if (info.event.extendedProps && info.event.extendedProps.isHoliday) {
                Swal.fire({
                    title: info.event.title,
                    text: 'Dies ist ein gesetzlicher Feiertag. Das heißt keine Arbeit :)',
                    icon: 'info',
                    confirmButtonText: 'OK'
                });
                return;
            }

            const eventTitle = info.event.title || '';
            const usernameLower = username.trim().toLowerCase();
            const isOwnEvent =
                eventTitle.trim().toLowerCase().endsWith(' - ' + usernameLower) ||
                eventTitle.trim().toLowerCase() === usernameLower;

            if (!isOwnEvent) {
                Swal.fire({
                    title: 'Nicht erlaubt',
                    text: 'Sie können nur Ihre eigenen Ereignisse bearbeiten.',
                    icon: 'warning',
                    confirmButtonText: 'OK'
                });
                return;
            }

            info.jsEvent.preventDefault();

            function getDateInputValue(dateObj) {
                if (!dateObj) {
                    const now = new Date();
                    return now.toISOString().slice(0, 10);
                }
                if (dateObj instanceof Date) {
                    const year = dateObj.getFullYear();
                    const month = String(dateObj.getMonth() + 1).padStart(2, '0');
                    const day = String(dateObj.getDate()).padStart(2, '0');
                    return `${year}-${month}-${day}`;
                }
                if (typeof dateObj === 'string') {
                    return dateObj.slice(0, 10);
                }
                return '';
            }

            const startValue = getDateInputValue(info.event.start);
            const endValue = '';

            const modal = document.createElement('div');
            modal.className = 'event-modal';
            modal.innerHTML = `
                <div class="modal-content">
                    <h3>Event bearbeiten</h3>
                    <label for="event-title">Titel:</label>
                    <input type="text" id="event-title" value="${info.event.title}">
                    <label for="event-description">Beschreibung:</label>
                    <textarea id="event-description">${info.event.extendedProps.description || ''}</textarea>
                    <label for="event-start">Start:</label>
                    <input type="date" id="event-start" value="${startValue}">
                    <label for="event-end">Ende:</label>
                    <input type="date" id="event-end" value="${endValue}">
                    <div class="button-container">
                        <button id="save-event">Speichern</button>
                        <button id="delete-event">Löschen</button>
                    </div>
                    <button id="close-modal">Schließen</button>
                </div>
            `;
            document.body.appendChild(modal);

            document.getElementById('save-event').addEventListener('click', function() {
                const newTitle = document.getElementById('event-title').value;
                const newDescription = document.getElementById('event-description').value;
                const newStart = document.getElementById('event-start').value;
                const newEnd = document.getElementById('event-end').value;

                function dateToISO(dateStr) {
                    if (!dateStr) return null;
                    return dateStr + 'T00:00:00';
                }
                const startISO = dateToISO(newStart);
                const endISO = dateToISO(newEnd);

                Swal.fire({
                    title: 'Speichern',
                    text: 'Möchten Sie die Änderungen speichern?',
                    icon: 'question',
                    showCancelButton: true,
                    confirmButtonText: 'Ja, speichern',
                    cancelButtonText: 'Abbrechen'
                }).then((result) => {
                    if (result.isConfirmed) {
                        info.event.setProp('title', newTitle);
                        info.event.setExtendedProp('description', newDescription);
                        info.event.setStart(startISO ? startISO : null);
                        info.event.setEnd(endISO ? endISO : null);

                        const backgroundColor = info.event.backgroundColor || info.event.extendedProps.backgroundColor || null;

                        fetch('../../API/update_event.php', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json'
                            },
                            body: JSON.stringify({
                                id: info.event.id,
                                title: newTitle,
                                description: newDescription,
                                calendarType: calendarType,
                                start: startISO ? startISO : null,
                                end: endISO ? endISO : null,
                                background_color: backgroundColor
                            })
                        })
                        .then(response => response.json())
                        .then(data => {
                            if (data.success) {
                                Swal.fire('Gespeichert!', 'Die Änderungen wurden gespeichert.', 'success');
                            }
                        })
                        .catch(error => {
                            console.error('Error:', error);
                            Swal.fire('Fehler!', 'Es gab ein Problem beim Speichern der Änderungen.', 'error');
                        });

                        document.body.removeChild(modal);
                    }
                });
            });

            document.getElementById('delete-event').addEventListener('click', function() {
                Swal.fire({
                    title: 'Löschen',
                    text: 'Möchten Sie dieses Event wirklich löschen?',
                    icon: 'warning',
                    showCancelButton: true,
                    confirmButtonText: 'Ja, löschen',
                    cancelButtonText: 'Abbrechen'
                }).then((result) => {
                    if (result.isConfirmed) {
                        fetch('../../API/delete_event.php', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json'
                            },
                            body: JSON.stringify({
                                id: info.event.id,
                                calendarType: calendarType
                            })
                        })
                        .then(response => response.json())
                        .then(data => {
                            if (data.success) {
                                info.event.remove();
                                Swal.fire('Gelöscht!', 'Das Ereignis wurde gelöscht.', 'success');
                            }
                            document.body.removeChild(modal);
                        })
                        .catch(error => {
                            console.error('Error:', error);
                            Swal.fire('Fehler!', 'Es gab ein Problem beim Löschen des Ereignis.', 'error');
                            document.body.removeChild(modal);
                        });
                    }
                });
            });

            document.getElementById('close-modal').addEventListener('click', function() {
                document.body.removeChild(modal);
            });
        },
        eventContent: function(arg) {
            let title = arg.event.title || '';
            let desc = arg.event.extendedProps && arg.event.extendedProps.description ? arg.event.extendedProps.description : '';
            let descPreview = desc.length > 20 ? desc.substring(0, 20) + '...' : desc;
            let arrayOfDomNodes = [];
            let titleEl = document.createElement('div');
            titleEl.innerText = title;
            titleEl.className = 'event-title';
            arrayOfDomNodes.push(titleEl);
            if (descPreview) {
                let descEl = document.createElement('div');
                descEl.innerText = descPreview;
                descEl.className = 'event-desc-preview';
                arrayOfDomNodes.push(descEl);
            }
            return { domNodes: arrayOfDomNodes };
        },
        drop: function(info) {
            const eventData = JSON.parse(info.draggedEl.dataset.event);
            const newEvent = {
                title: eventData.title,
                start: info.dateStr,
                end: null,
                description: eventData.description || '',
                background_color: eventData.backgroundColor || null,
                calendarType: calendarType,
            };

            fetch('../../API/save_event.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(newEvent)
            })
            .then(response => response.json())
        },
        eventDrop: function(info) {
            fetch('../../API/update_event.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    id: info.event.id,
                    title: info.event.title,
                    description: info.event.extendedProps.description || '',
                    calendarType: calendarType,
                    start: info.event.start ? info.event.start.toISOString() : null,
                    end: info.event.end ? info.event.end.toISOString() : null
                })
            });
        },
        eventResize: function(info) {
            fetch('../../API/update_event.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    id: info.event.id,
                    title: info.event.title,
                    description: info.event.extendedProps.description || '',
                    calendarType: calendarType,
                    start: info.event.start ? info.event.start.toISOString() : null,
                    end: info.event.end ? info.event.end.toISOString() : null
                })
            });
        },
        eventOrder: function(a, b) {
            if (a.extendedProps && a.extendedProps.isHoliday) return -1;
            if (b.extendedProps && b.extendedProps.isHoliday) return 1;
            return 0;
        },
    });
    function getEasterDate(year) {
        var f = Math.floor,
            G = year % 19,
            C = f(year / 100),
            H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,
            I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),
            J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,
            L = I - J,
            month = 3 + f((L + 40) / 44),
            day = L + 28 - 31 * f(month / 4);
        return new Date(year, month - 1, day);
    }
    function getGermanHolidays(year) {
        const pad = n => String(n).padStart(2, '0');
        const holidays = [
            { title: 'Neujahr', date: `${year}-01-01` },
            { title: 'Heilige Drei Könige', date: `${year}-01-06` },
            { title: 'Tag der Arbeit', date: `${year}-05-01` },
            { title: 'Mariä Himmelfahrt', date: `${year}-08-15` },
            { title: 'Tag der Deutschen Einheit', date: `${year}-10-03` },
            { title: 'Reformationstag', date: `${year}-10-31` },
            { title: 'Allerheiligen', date: `${year}-11-01` },
            { title: '1. Weihnachtstag', date: `${year}-12-25` },
            { title: '2. Weihnachtstag', date: `${year}-12-26` }
        ];

        const easter = getEasterDate(year);
        const easterStr = `${easter.getFullYear()}-${pad(easter.getMonth() + 1)}-${pad(easter.getDate())}`;
        const goodFriday = new Date(easter); goodFriday.setDate(easter.getDate() - 2);
        const easterMonday = new Date(easter); easterMonday.setDate(easter.getDate() + 1);
        const ascension = new Date(easter); ascension.setDate(easter.getDate() + 39);
        const pentecost = new Date(easter); pentecost.setDate(easter.getDate() + 49);
        const pentecostMonday = new Date(easter); pentecostMonday.setDate(easter.getDate() + 50);
        const corpusChristi = new Date(easter); corpusChristi.setDate(easter.getDate() + 60);

        holidays.push(
            { title: 'Karfreitag', date: `${goodFriday.getFullYear()}-${pad(goodFriday.getMonth() + 1)}-${pad(goodFriday.getDate())}` },
            { title: 'Ostersonntag', date: easterStr },
            { title: 'Ostermontag', date: `${easterMonday.getFullYear()}-${pad(easterMonday.getMonth() + 1)}-${pad(easterMonday.getDate())}` },
            { title: 'Christi Himmelfahrt', date: `${ascension.getFullYear()}-${pad(ascension.getMonth() + 1)}-${pad(ascension.getDate())}` },
            { title: 'Pfingstsonntag', date: `${pentecost.getFullYear()}-${pad(pentecost.getMonth() + 1)}-${pad(pentecost.getDate())}` },
            { title: 'Pfingstmontag', date: `${pentecostMonday.getFullYear()}-${pad(pentecostMonday.getMonth() + 1)}-${pad(pentecostMonday.getDate())}` },
            { title: 'Fronleichnam', date: `${corpusChristi.getFullYear()}-${pad(corpusChristi.getMonth() + 1)}-${pad(corpusChristi.getDate())}` }
        );
        return holidays;
    }

    const year = new Date().getFullYear();
    const holidays = getGermanHolidays(year).map(h => ({
        title: h.title,
        start: h.date,
        allDay: true,
        editable: false,
        durationEditable: false,
        backgroundColor: '#ffe0e0',
        borderColor: '#ffaaaa',
        textColor: '#b30000',
        display: 'block',
        isHoliday: true,
        classNames: ['is-holiday-event']
    }));
    holidays.forEach(event => calendar.addEvent(event));

    fetch(`../../API/get_events.php?calendarType=${calendarType}`)
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                data.events.forEach(event => {
                    calendar.addEvent({
                        ...event,
                        backgroundColor: event.background_color || undefined,
                        editable: true,
                        durationEditable: true,
                    });
                });
            }
        })
        .catch(error => {
            console.error('Error:', error);
        });

    calendar.render();

    var externalEvents = [
        { title: `Geplant - ${username}`, id: '1', backgroundColor: '#FFDE97', description: 'Geplanter Urlaub' },
        { title: `Bestätigt - ${username}`, id: '2', backgroundColor: '#BEF7C1', description: 'Bestätigter Urlaub' },
        { title: `Homeoffice - ${username}`, id: '3', backgroundColor: '#C3C1FD', description: 'Im Homeoffice' },
        { title: `Berufsschule - ${username}`, id: '4', backgroundColor: '#F3B7B7', description: 'In der Berufsschule' },
    ];

    var eventList = document.getElementById('external-events');
    externalEvents.forEach(function(event) {
        var eventEl = document.createElement('li');
        eventEl.className = 'fc-event';
        let descPreview = event.description ? event.description.substring(0, 20) : '';
        if (event.description && event.description.length > 20) descPreview += '...';
        eventEl.innerHTML = `<span class="event-title">${event.title}</span><br><span class="event-desc-preview">${descPreview}</span>`;
        eventEl.dataset.event = JSON.stringify(event);
        eventList.appendChild(eventEl);
    });

    new FullCalendar.Draggable(eventList, {
        itemSelector: '.fc-event',
        eventData: function(eventEl) {
            const data = JSON.parse(eventEl.dataset.event);
            if (data.backgroundColor) {
                data.backgroundColor = data.backgroundColor;
            }
            return data;
        }
    });

        const buttons = document.querySelectorAll('.event-filter-btn');

        function resetCalendarFilter() {
            document.querySelectorAll('.fc-event').forEach(ev => {
                ev.classList.remove('calendar-event-faded');
            });
            buttons.forEach(b => {
                b.classList.remove('active-filter');
                b.style.opacity = '1';
            });
        }

        buttons.forEach(btn => {
            btn.addEventListener('mousedown', function(e) {
                e.preventDefault();
            });
            btn.addEventListener('click', function(e) {
                e.stopPropagation();
                if (btn.classList.contains('active-filter')) {
                    resetCalendarFilter();
                    return;
                }
                buttons.forEach(b => b.classList.remove('active-filter'));
                btn.classList.add('active-filter');
                const type = btn.dataset.type;

                document.querySelectorAll('.fc-event').forEach(ev => {
                    ev.classList.remove('calendar-event-faded');
                });

                document.querySelectorAll('.fc-event').forEach(ev => {
                    const titleEl = ev.querySelector('.event-title');
                    const title = titleEl ? titleEl.textContent : ev.textContent || '';
                    if (!title.toLowerCase().includes(type.toLowerCase())) {
                        ev.classList.add('calendar-event-faded');
                    }
                });

                buttons.forEach(b => {
                    if (b !== btn) b.style.opacity = '0.3';
                    else b.style.opacity = '1';
                });
            });
        });

        document.addEventListener('click', function(e) {
            const isButton = e.target.closest('.event-filter-btn');
            if (!isButton) {
                resetCalendarFilter();
            }
        });

    function updateStarButtons() {
        const favs = JSON.parse(localStorage.getItem('favoriteCalendars') || '[]');
        document.querySelectorAll('.star-btn').forEach(btn => {
            const cal = btn.dataset.calendar;
            const star = btn.querySelector('.star');
            if (favs.includes(cal)) {
                star.innerHTML = '★';
                star.style.color = 'gold';
            } else {
                star.innerHTML = '☆';
                star.style.color = '#bbb';
            }
        });
    }
    document.querySelectorAll('.star-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const cal = this.dataset.calendar;
            let favs = JSON.parse(localStorage.getItem('favoriteCalendars') || '[]');
            if (favs.includes(cal)) {
                favs = favs.filter(f => f !== cal);
            } else {
                favs.push(cal);
            }
            localStorage.setItem('favoriteCalendars', JSON.stringify(favs));
            updateStarButtons();
        });
    });
    updateStarButtons();
});

This is the database file:

class Database {
    private static $connection = null;

    function __construct() {
        if (self::$connection == null) {
            $env = parse_ini_file(__DIR__ . '/../.env');

            self::$connection = new SQLite3($env["DATABASE_PATH"], SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);    
            self::$connection->enableExceptions(true);
        }
    }
public function saveEvent($title, $start, $end = null, $description = null, $background_color = null, $calendarType = 'entwicklung') {
        $table = $this->getEventTable($calendarType);
        $stmt = self::$connection->prepare("INSERT INTO $table (title, start, end, description, background_color) VALUES (:title, :start, :end, :description, :background_color)");
        $stmt->bindValue(':title', $title, SQLITE3_TEXT);
        $stmt->bindValue(':start', $start, SQLITE3_TEXT);
        $stmt->bindValue(':end', $end, SQLITE3_TEXT);
        $stmt->bindValue(':description', $description, SQLITE3_TEXT);
        $stmt->bindValue(':background_color', $background_color, SQLITE3_TEXT);
        $stmt->execute();
    }

    public function getEvents($calendarType = 'entwicklung') {
        if ($calendarType === 'gesamt') {
            $query = "SELECT id, title, start, end, description, background_color, 'entwicklung' as calendarType FROM event_entwicklung
                      UNION ALL
                      SELECT id, title, start, end, description, background_color, 'support' as calendarType FROM event_support";
            $result = self::$connection->query($query);
        } else {
            $table = $this->getEventTable($calendarType);
            $stmt = self::$connection->prepare("SELECT id, title, start, end, description, background_color FROM $table");
            $result = $stmt->execute();
        }
        $events = [];
        while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
            $events[] = $row;
        }
        return $events;
    }

    public function updateEvent($id, $title, $description = null, $calendarType = 'entwicklung', $start = null, $end = null) {
        $table = $this->getEventTable($calendarType);
        $sql = "UPDATE $table SET title = :title, description = :description";
        if ($start !== null) $sql .= ", start = :start";
        if ($end !== null) $sql .= ", end = :end";
        $sql .= " WHERE id = :id";
        $stmt = self::$connection->prepare($sql);
        $stmt->bindValue(':id', $id, SQLITE3_INTEGER);
        $stmt->bindValue(':title', $title, SQLITE3_TEXT);
        $stmt->bindValue(':description', $description, SQLITE3_TEXT);
        if ($start !== null) $stmt->bindValue(':start', $start, SQLITE3_TEXT);
        if ($end !== null) $stmt->bindValue(':end', $end, SQLITE3_TEXT);
        $stmt->execute();
    }

    public function deleteEvent($id, $calendarType = 'entwicklung') {
        $table = $this->getEventTable($calendarType);
        $stmt = self::$connection->prepare("DELETE FROM $table WHERE id = :id");
        $stmt->bindValue(':id', $id, SQLITE3_INTEGER);
        $stmt->execute();
    }

This is the save_event file:

<?php
require_once __DIR__ . '/../../Backend/database.php';

header('Content-Type: application/json');

$data = json_decode(file_get_contents('php://input'), true);

$title = $data['title'] ?? '';
$start = $data['start'] ?? '';
$end = $data['end'] ?? null;
$description = $data['description'] ?? null;
$background_color = $data['background_color'] ?? null;
$calendarType = $data['calendarType'] ?? 'entwicklung';

try {
    $db = new Database();
    $db->saveEvent($title, $start, $end, $description, $background_color, $calendarType);
    echo json_encode(['success' => true]);
} catch (Exception $e) {
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>

This is the get_event file:

<?php
require_once __DIR__ . '/../../Backend/database.php';

header('Content-Type: application/json');

$calendarType = isset($_GET['calendarType']) ? $_GET['calendarType'] : 'entwicklung';

try {
    $db = new Database();
    $events = $db->getEvents($calendarType);
    echo json_encode(['success' => true, 'events' => $events]);
} catch (Exception $e) {
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>

and this the update_event file:

<?php
require_once __DIR__ . '/../../Backend/database.php';

header('Content-Type: application/json');

$data = json_decode(file_get_contents('php://input'), true);

$id = $data['id'] ?? null;
$title = $data['title'] ?? '';
$description = $data['description'] ?? null;
$calendarType = $data['calendarType'] ?? 'entwicklung';
$start = $data['start'] ?? null;
$end = $data['end'] ?? null;

if ($id === null) {
    echo json_encode(['success' => false, 'error' => 'ID fehlt']);
    exit;
}

try {
    $db = new Database();
    $db->updateEvent($id, $title, $description, $calendarType, $start, $end);
    echo json_encode(['success' => true]);
} catch (Exception $e) {
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
?>

Could someone help me ?

Getting undefined on a value that is obviously defined

I am getting this error (upon login with biometrics):

Login verification error: TypeError: Cannot read properties of undefined (reading 'counter')
    at verifyAuthenticationResponse (file:///home/andrew/project/node_modules/@simplewebauthn/server/esm/authentication/verifyAuthenticationResponse.js:144:36)
    at async file:///home/andrew/project/bin/bioMetricServer.js:284:26

Here’s the entire code:

app.post('/webauthn/login/verify', async (req, res) => {
  const { username, credential } = req.body;
  const expectedChallenge = getChallenge(username);
  const userCredentials = await db.getCredentialsByUsername(username);

  console.log("Verifying login for username:", username);
  console.log("Expected challenge:", expectedChallenge);
  console.log("Credentials from DB:", userCredentials);


  if (!userCredentials || userCredentials.length === 0 || !expectedChallenge) {
    return res.status(400).json({ error: 'Missing challenge or user not found' });
  }

  const credentialID = Buffer.from(credential.rawId, 'base64url');

  const authnCred = userCredentials.find((c) =>
    Buffer.compare(c.id, credentialID) === 0
  );

  if (!authnCred) {
    return res.status(400).json({ error: 'Authenticator not registered for user' });
  }

  // console.log("Authenticator counter:", authnCred.counter);

  console.log("Calling verifyAuthenticationResponse with:");
  console.log("authenticator:", {
    credentialID: authnCred.id,
    credentialPublicKey: Buffer.from(authnCred.publicKey),
    counter: authnCred.counter,
  });

  console.log("Credential.response keys:", Object.keys(credential.response));
  console.log("authenticatorData length:", credential.response.authenticatorData?.length);

  console.log('Authenticator sent to verifyAuthenticationResponse:', {
    credentialID: authnCred.id,
    credentialPublicKey: Buffer.from(authnCred.publicKey),
    counter: authnCred.counter,
  });
  
  console.log('Type of counter:', typeof authnCred.counter);
  console.log('Is counter defined?', authnCred.counter !== undefined);

  try {
    const verification = await verifyAuthenticationResponse({
      response: credential,
      expectedChallenge,
      expectedOrigin: EXPECTED_ORIGIN,
      expectedRPID: EXPECTED_RPID,
      authenticator: {
        credentialID: authnCred.id,
        credentialPublicKey: Buffer.from(authnCred.publicKey),
        counter: authnCred.counter,
      },
    });

    const { verified, authenticationInfo } = verification;

    if (verified) {
      // Update counter to prevent replay attacks
      await db.updateCredentialCounter(username, authnCred.id, authenticationInfo.newCounter);

      return res.json({ success: true });
    }

    res.status(400).json({ verified: false });
  } catch (err) {
    console.error('Login verification error:', err);
    res.status(400).json({ error: err.message });
  }
});

As you can see in the code, I have a lot of console.log, which might be helpful to see, so I’ll add the log below:

Server running on http://localhost:3000
Verifying login for username: peter
Expected challenge: HCqJmSJct85xx1G8IbQ7werJDjIIeBziPtSLvPXAfgM

Credentials from DB: [
  {
    id: <Buffer e8 80 ac dd 70 62 86 4d 4d d2 c7 13 90 65 40 1b f6 5f 80 34>,
    publicKey: <Buffer d7 ae 75 db 7d fc df 6d 77 df cf 37 db 9d fa e7 6d f7 d7 8d 35 e3 5d 7b d7 5d 3b d7 9e fa f3 ad 77 d7 9e 75 d3 5d f5 d7 7e f5 db 5e 35 d7 dd 36 df 8d ... 94 more bytes>,
    counter: 0
  }
]
Calling verifyAuthenticationResponse with:
authenticator: {
  credentialID: <Buffer e8 80 ac dd 70 62 86 4d 4d d2 c7 13 90 65 40 1b f6 5f 80 34>,
  credentialPublicKey: <Buffer d7 ae 75 db 7d fc df 6d 77 df cf 37 db 9d fa e7 6d f7 d7 8d 35 e3 5d 7b d7 5d 3b d7 9e fa f3 ad 77 d7 9e 75 d3 5d f5 d7 7e f5 db 5e 35 d7 dd 36 df 8d ... 94 more bytes>,
  counter: 0
}

Credential.response keys: [ 'authenticatorData', 'clientDataJSON', 'signature', 'userHandle' ]
authenticatorData length: 50
Authenticator sent to verifyAuthenticationResponse: {
  credentialID: <Buffer e8 80 ac dd 70 62 86 4d 4d d2 c7 13 90 65 40 1b f6 5f 80 34>,
  credentialPublicKey: <Buffer d7 ae 75 db 7d fc df 6d 77 df cf 37 db 9d fa e7 6d f7 d7 8d 35 e3 5d 7b d7 5d 3b d7 9e fa f3 ad 77 d7 9e 75 d3 5d f5 d7 7e f5 db 5e 35 d7 dd 36 df 8d ... 94 more bytes>,
  counter: 0
}

Type of counter: number
Is counter defined? true

The function that gets credentials from the database, looks like this

db.getCredentialsByUsername = async (username) => {
  const rows = await db('webauthn_credentials')
    .where({ isousername: username });

  return rows.map(row => ({
    id: Buffer.from(row.credential_id, 'base64'),
    publicKey: Buffer.from(row.public_key, 'base64'),
    counter: row.counter,
  }));
};

The testdata in my database looks like this (the counter is there):

id | isousername |        credential_id        |                                                                                            public_key                                                                                            | counter |          created_at           
----+-------------+-----------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+-------------------------------
  2 | peter        | 6ICs3XBihk1N0scTkGVAG_ZfgDQ | 165123383213388325365233140141171107157686131551013113712141190234017320211361592421381435613915420434883239138205114232184741111561072374710180148610211622324510021110172261273825010223410820 |       0 | 2025-05-19 09:11:40.945938+02
(1 row)

I’ve run into a wall for several days, and can’t figure it out.
I think that the main problem is here:

const verification = await verifyAuthenticationResponse({
      response: credential,
      expectedChallenge,
      expectedOrigin: EXPECTED_ORIGIN,
      expectedRPID: EXPECTED_RPID,
      authenticator: {
        credentialID: authnCred.id,
        credentialPublicKey: Buffer.from(authnCred.publicKey),
        counter: authnCred.counter,
      },
    });

It can be seen in all the console.log, that authnCred.counter is not undefined, and it’s of type Number, as should be, but counter: authnCred.counter is undefined.

Let me know if you need to see any more/other file or code

Available dates are not being properly rendered on initial load despite correct data in props

Here is calendar component. I have three active dates in the coach’s schedule (25th–27th), where the coach has available slots for booking sessions. However, along with these active dates, all Saturdays and Sundays of every month are also being displayed for some reason.

import { format, isBefore, startOfDay } from "date-fns";
import { FC } from "react";
import Calendar from "react-calendar";
import { Value } from "react-calendar/dist/esm/shared/types.js";

import styles from "@/pages/booking-page/styles.module.scss";
import { CalendarValue } from "@/types/sessions.ts";

interface BookingCalendarProps {
  selectedDate: Date | null;
  activeDate: Date;
  onDateChange: (value: CalendarValue) => void;
  availableDates: string[];
  currentDate: Date;
}

const BookingCalendar: FC<BookingCalendarProps> = ({
  selectedDate,
  activeDate,
  onDateChange,
  availableDates,
  currentDate,
}) => {
  const sanitizedAvailableDates = (availableDates || []).filter(
    (date) => typeof date === "string" && date.match(/^d{4}-d{2}-d{2}$/),
  );

  console.log("Sanitized available dates:", sanitizedAvailableDates);

  const handleDateChange = (value: Value) => {
    if (value instanceof Date) {
      onDateChange(value);
    }
  };

  console.log("Calendar received availableDates:", availableDates);

  return (
    <Calendar
      onChange={handleDateChange}
      value={selectedDate}
      activeStartDate={activeDate}
      minDate={currentDate}
      locale="en-US"
      className={styles.calendar}
      tileClassName={({ date }) => {
        const formattedDate = format(date, "yyyy-MM-dd");

        if (
          selectedDate &&
          date.toDateString() === selectedDate.toDateString()
        ) {
          return styles.selectedDate;
        }

        console.log(
          "sanitizedAvailableDates.includes(formattedDate) ",
          sanitizedAvailableDates.includes(formattedDate),
        );

        if (sanitizedAvailableDates.includes(formattedDate)) {
          return styles.availableDate;
        }

        return null;
      }}
      tileDisabled={({ date }) => {
        if (isBefore(date, startOfDay(currentDate))) {
          return true;
        }

        const formattedDate = format(date, "yyyy-MM-dd");

        if (!availableDates || availableDates.length === 0) {
          return false;
        }

        const isAvailable = sanitizedAvailableDates.includes(formattedDate);

        console.log("isAvailable ===>>>", isAvailable);
        console.log(
          `Date: ${formattedDate}, Available: ${isAvailable}, Disabled: ${!isAvailable}, Valid dates count: ${sanitizedAvailableDates.length}`,
        );

        return !isAvailable;
      }}
    />
  );
};

export default BookingCalendar;

$primary-color: #22b8cf;
$secondary-color: #f1f1f1;
$border-color: #e0e0e0;
$text-color: #333;
$light-gray: #f8f9fa;
$highlight-color: #eaf9fc;
$box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
$border-radius: 12px;
$button-hover: #1a9db1;
$error-color: #ff6b6b;
@use "sass:map";
@use "@/styles/vars" as v;
@use "@/styles/_mixins" as m;

.bookingPage {
  font-family: Lexend, serif;
  color: $text-color;
  padding: 40px 0;
}

.backButton {
  margin-bottom: 10px;
  margin-right: 8px;
}

.backArrow {
  width: 30px;
  height: 30px;
  border-radius: 30%;
  background-color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  font-size: 16px;
  position: relative;

  svg {
    transform: rotate(180deg);
  }
}

.backButtonWrapper {
  display: flex;
  align-items: center;
  gap: 10px;
}

.backIcon {
  font-size: 20px;
}

.title {
  font-size: 24px;
  font-weight: 600;
  margin-bottom: 10px;
}

.bookingCard {
  background-color: white;
  border-radius: $border-radius;
  box-shadow: $box-shadow;
  display: flex;
  overflow: hidden;

  @media (max-width: 768px) {
    flex-direction: column;
  }
}

.bookingDetails {
  flex: 1;
  padding: 30px;
  border-right: 1px solid $border-color;

  @media (max-width: 768px) {
    border-right: none;
    border-bottom: 1px solid $border-color;
  }
}

.sessionTitle {
  font-size: 20px;
  margin-top: 0;
  margin-bottom: 24px;
}

.detailItem {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  font-size: 15px;
  max-width: 360px;
  word-wrap: break-word;

  .icon {
    width: 20px;
    display: inline-flex;
    justify-content: center;
    & svg {
      fill: $primary-color;
    }
  }
}

.optionTitle {
  font-size: 18px;
  margin: 30px 0 16px;
}

.buttonBlock {
  width: 100%;
  text-align: right;
}

.bookButton {
  width: 100%;
  max-width: 88px;
  padding: 14px;
  background-color: $primary-color;
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  margin-top: 30px;
  transition: background-color 0.2s ease;

  &:hover:not(:disabled) {
    background-color: $button-hover;
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

.errorMessage {
  display: flex;
  align-items: center;
  background-color: #ffeeee;
  border: 1px solid $error-color;
  border-radius: 6px;
  padding: 12px 16px;
  margin-top: 20px;
  font-size: 14px;

  .errorIcon {
    margin-right: 8px;
    color: $error-color;
  }

  .closeButton {
    margin-left: auto;
    background: none;
    border: none;
    cursor: pointer;
    color: $text-color;
    font-size: 16px;
  }
}

.calendarSection {
  flex: 1.5;
  padding: 30px 30px 0 30px;
  display: flex;
  flex-direction: column;
}

.calendarHeader {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.calendarTitle {
  font-size: 18px;
  margin: 0;
}

.availableDate {
  background-color: rgba(0, 255, 0, 0.1);
  font-weight: bold;
}

.prevButton {
  transform: rotate(180deg);
}

.prevButton, .nextButton {
  background: none;
  border: none;
  font-size: 18px;
  cursor: pointer;
  padding: 5px 10px;
  color: $text-color;

  &:hover {
    background-color: $light-gray;
    border-radius: 4px;
  }
}

.calendarContainer {
  display: flex;
  gap: 20px;
  height: 100%;

  @media (max-width: 1024px) {
    flex-direction: column;
  }
}

.calendarWrapper {
  flex: 1;
}

.timeSlotsWrapper {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  min-width: 120px;
  border-left: 1px dashed map.get(v.$colors-grayscale, '10');

  .noTimeSlotsMessage {
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 8px;

    p {
      width: 100%;
      max-width: 120px;
      text-align: center;
      margin-left: 20px;
    }
  }

  @media (max-width: 1024px) {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 10px;
  }
}

.calendar {
  width: 100%;
  border: none;
  font-family: inherit;

  :global {
    .react-calendar__navigation {
      display: none;
    }

    .react-calendar__month-view__weekdays {
      text-align: center;
      text-transform: uppercase;
      font-weight: 600;
      font-size: 14px;
      padding-bottom: 10px;

      abbr {
        text-decoration: none;
      }
    }

    .react-calendar__month-view__days {
      display: grid !important;
      grid-template-columns: repeat(7, 1fr);
      gap: 8px;
    }

    .react-calendar__month-view__days__day {
      max-width: 100%;
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 50%;
      margin: 0 auto;
      aspect-ratio: 1/1;
      font-size: 14px;

      &:hover {
        background-color: $highlight-color;
      }

      &--weekend {
        color: inherit;
      }
    }

    .react-calendar__tile {
      max-width: initial !important;
      background-color: transparent;
      line-height: normal;
      width: 40px;
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      margin: 0 auto;
      padding: 0;
      position: relative;

      &--active {
        background-color: map.get(v.$colors-primary, "5") !important;
        color: v.$color-blue !important;
        border: 1px solid $primary-color !important;
        border-radius: 20%;

        &:hover {
          background-color: map.get(v.$colors-primary, "5") !important;
        }
      }

      &--now {
        background-color: transparent;
        border: 1px solid $primary-color;
        border-radius: 20%;
      }
    }

    .react-calendar__month-view__weekdays__weekday {
      padding: 0.5em;
      text-align: center;
    }
  }
}

.selectedDate {
  background-color: map.get(v.$colors-primary, "5") !important;
  border: 1px solid $primary-color !important;
  color: v.$color-blue !important;
  border-radius: 20% !important;
}

Also, here’s what console.log returns to me.

 index.tsx:28 Sanitized available dates: (3) ['2025-05-26', '2025-05-27', '2025-05-25']
index.tsx:36 Calendar received availableDates: (3) ['2025-05-26', '2025-05-27', '2025-05-25']
23index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-19, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-20, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-21, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-22, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-23, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-24, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  true
index.tsx:80 isAvailable ===>>> true
index.tsx:81 Date: 2025-05-25, Available: true, Disabled: false, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  true
index.tsx:80 isAvailable ===>>> true
index.tsx:81 Date: 2025-05-26, Available: true, Disabled: false, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  true
index.tsx:80 isAvailable ===>>> true
index.tsx:81 Date: 2025-05-27, Available: true, Disabled: false, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-28, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-29, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-30, Available: false, Disabled: true, Valid dates count: 3
index.tsx:56 sanitizedAvailableDates.includes(formattedDate)  false
index.tsx:80 isAvailable ===>>> false
index.tsx:81 Date: 2025-05-31, Available: false, Disabled: true, Valid dates count: 3

Expo 53.0.9 splash screen issue

I’m currently working with Expo version 53.0.9. I want to customize the splash screen, but the example provided in the expo-splash-screen package references App.tsx, which is no longer included in the latest Expo project structure. How can I customize the splash screen in Expo 53?

Show warning pop up when page reload/refresh in ios safari/crome browser

window.addEventListener('beforeunload', function (e) {
     const thankYouVisible = document.querySelector('.thank-you')?.offsetParent !== null;
        if (!thankYouVisible) {
           const confirmationMessage = "Are you sure you want to leave this page? Your 
 progress may be lost.";
          e.preventDefault();
          e.returnValue = confirmationMessage;
          return confirmationMessage;
        }
     });

Above code is working code for Desktop

Now below code is for ios safari/crome

Now issue is that first code for desktop is working fine for desktop But now case of ios/android it is not working so I have put below code

  window.addEventListener('pagehide', () => {
    const thankYouVisible = document.querySelector('.thank-you')?.offsetParent !== null;
     if (!thankYouVisible) {
       localStorage.setItem('triedToLeave', '1');
     }
   });

    window.addEventListener('DOMContentLoaded', () => {
    if (localStorage.getItem('triedToLeave') === '1') {
        localStorage.removeItem('triedToLeave');
        const shouldRefresh = confirm('It looks like you tried to leave the page. Do you want to refresh?');

      if (shouldRefresh) {
         location.reload(); // Refresh only if user clicks "OK"
      }
    }
  });

Now the code is working — the warning popup appears with Yes/No options. But the page refreshes before the popup is shown, so if I click No, it doesn’t work because the page has already reloaded.

I’m working on a quiz where the user answers questions. If the user tries to reload the page in the middle of the quiz, a warning popup appears saying: “Are you sure you want to reload the page? Your data may be lost.” This works fine on desktop browsers, but it does not work on iPhone in Chrome or Safari.

Tree structure display in React [closed]

I need to render the following on a React app . There are only two groups possible with a max 3 items in each group and the first box is creator information.

I have not filled the other two items in GroupA in the snapshot as it follows same format as item1. How do I create this structure with the lines connecting from creator to groups in JSX and render the UI? All the circles would be light grey and individual box background in light blue enter image description here

 {
     Creator : "Andy Bi",
     CreatedDate : "02/02/2025",
     CreatorInitials : "AB"
     GroupA : [
      { id: 1,
        Name : "Jam Bin",
        Initials : "JB"
        Date : "01/01/2015"
       },
       { id: 2,
        Name : "Jim Frog",
        Initials : "JF",
        Date : "03/01/2022"
       },
       {
        id : 3,
        Name : "Alex Xin",
        Initials : "AX",
        Date : 04/02/2023"
       }
      ],
      GroupB : [
      {
        id : 1,
        Name : "Zim Pin",
        Initials : "ZP",
        Date : 04/02/2023"
       },
       {
        id : 2,
        Name : "Am Bek",
        Initials : "AB",
        Date : 04/06/2022"
       },
     ]

How to inspect and locate the date picker field in the mobile application, when the application use another library (react-native-date-picker)

I am trying to handle the date picker field in android mobile application, but the application use the date picker i.e react-native-date-picker and this is the another library used in the application and I am not able to locate the date month year in the source code/DOM. so how can i handle the date picker field when the application use react-native-date-picker.

enter image description here

Userscripting and Manifest V3: Possible Solution?

I’ve been thinking about how we could reconcile userscripting with Manifest V3. In my opinion, userscripting is a really useful tool that has effectively become restricted by the new Manifest V3 rules.

An interesting idea came to my mind, and I would like to discuss it with you because I have limited experience and can’t fully judge whether it would work or not.

Here’s the idea: since we can’t legally use a single extension to run multiple scripts on different domains, why not create an extension that dynamically generates new extensions containing the user’s script?

For example:
Old way (Manifest V2): A user writes some code, specifies the target sites, and loads it New way (Manifest V3): A user writes some code and specifies the target sites inside a ‘Generator’ extension, which then creates a fully functional extension file. The user installs this generated extension themselves and gets the same functionality as before.

What do you think about this approach?