Is there any API or service to query issued and received invoices for a Mexican RFC (without generating invoices)?

I’m currently developing an application where I need to retrieve the invoices (CFDIs) issued to and from a specific Mexican RFC. I don’t need to generate or send invoices — just read the existing ones (issued and received) for data analysis and tracking purposes.

I explored SAT’s web services, but they seem oriented towards PACs (authorized providers) and invoice generation, not just reading.

What I’m expecting:

An API (official or not) that:
Allows programmatic access to a list of invoices (issued and received) for an RFC.
Works with credentials or tokens (e.g., e.firma or CIEC).
Doesn’t require me to issue invoices, only consult existing ones.

Using standard JS functions for reusable Jest test cases?

I am building a web API where I want to test certain conditions on each controller class. So for instance, I might have:

  • CompaniesController
  • JobsController
  • UsersController

I have certain test cases that I want to run on each endpoint. Things like:

  1. Checking that authentication is required
  2. Checking that authorization works as expected
  3. Verifying common payloads (e.g. that list endpoints are paginated)

What I’d like to do is maintain per-controller/class test files, but reuse groups of tests so that I’m not copying/pasting a bunch of code. This is different than the describe.each and test.each functionality, because that would require me to test ALL of my controllers in one large test file (like auth.test.ts or something). That’s not what I want to do. I want to organize my tests by logical unit (e.g. by controller) but be able to do something like…

describe('UsersController', () => {
  describe('GET /v1/users/{uuid}', () => {
    doAuthTests(/* some params here */);
  });
});

Where I could define something like:

function doAuthTests(/* params */) {
  beforeEach(() => {
    // set up the mocks based on the parameters
  });

  it('should require authentication', () => {
    // do the test that requires a valid Bearer token
  });

  it('should require authorization', () => {
    // do tests for things like API scopes
  });
}

The problem I’m facing is that it seems like the beforeEach/it blocks queue up or register their actions (rather than running them). So the parameters I pass all come through as undefined, even though I’m not using them until the it block fires.

Is there a way to ball up tests as a kind of “template” so that I can call them from multiple test files? This seems like a no-brainer kind of functionality for any large test suite.

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

Fatal error with Stripe Class ‘StripeStripe’ not found in Woocommerce

i’m trying to charge customer when they has order status upaid.

and i trigger it by one admin action button from wp-admin side.

problem is when i try to charge i’m getting error StripeStripe’ not found

i check woocommerce stripe getway folder there is no library to call autoload in my code.

need help to fix my code. my code is given below.

function handle_charge_unpaid_order() {
    if (
        !isset($_GET['order_id']) ||
        !wp_verify_nonce($_GET['_wpnonce'], 'charge_unpaid_order_' . $_GET['order_id'])
    ) {
        wp_die('Invalid or missing nonce');
    }

    $order_id = intval($_GET['order_id']);
    $order = wc_get_order($order_id);

    if (!$order || $order->get_status() !== 'unpaid') {
        wp_die('Invalid order or not unpaid');
    }

    if ($order->get_payment_method() !== 'stripe') {
        wp_die('Order does not use Stripe');
    }

    $customer_id   = $order->get_meta('_stripe_customer_id', true);
    $card_sourceid = $order->get_meta('_stripe_source_id', true);

    if (!$customer_id || !$card_sourceid) {
        wp_die('No Stripe customer or saved payment method found.');
    }

    // Load Stripe keys
    $options   = get_option('woocommerce_stripe_settings');
    $test_mode = isset($options['testmode']) && $options['testmode'] === 'yes';
    $secret_key = $test_mode ? $options['test_secret_key'] : $options['secret_key'];

    if (!$secret_key) {
        wp_die('Stripe API key not configured.');
    }
   // ✅ Ensure Stripe SDK is loaded
    if ( ! class_exists( 'StripeStripe' ) ) {
        if ( defined( 'WC_STRIPE_PLUGIN_PATH' ) ) {
            require_once WC_STRIPE_PLUGIN_PATH . '/includes/libraries/stripe-client/init.php';
        } else {
            wp_die('Stripe SDK not found. Please make sure the WooCommerce Stripe plugin is active.');
        }
    }

    StripeStripe::setApiKey($secret_key);

    try {
        $payment_intent = StripePaymentIntent::create([
            'amount' => intval(round($order->get_total() * 100)), // Convert to cents
            'currency' => strtolower(get_woocommerce_currency()),
            'customer' => $customer_id,
            'payment_method' => $card_sourceid,
            'off_session' => true,
            'confirm' => true,
            'metadata' => [
                'order_id' => $order->get_id(),
                'site' => get_bloginfo('name'),
            ],
        ]);

        $order->payment_complete($payment_intent->id);
        $order->add_order_note('Stripe payment successful. PaymentIntent ID: ' . $payment_intent->id);

        wp_redirect(admin_url('admin.php?page=wc-orders&action=edit&id=' . $order_id));
        exit;

    } catch (StripeExceptionCardException $e) {
        $order->update_status('failed');
        $order->add_order_note('Stripe card error: ' . $e->getMessage());
        wp_die('Stripe Card Error: ' . esc_html($e->getMessage()));
    } catch (Exception $e) {
        $order->update_status('failed');
        $order->add_order_note('Stripe charge failed: ' . $e->getMessage());
        wp_die('Stripe Error: ' . esc_html($e->getMessage()));
    }
} ```

Can’t access public property [closed]

I’ve set public property and define it in construct as following :

<?php

use AppModelsPlan;

class ClassB extends Base
{
    public $plan;

    public function __construct()
    {
        $this->plan = new Plan();
    }

    public function viewPlan()
    {
        View::renderTemplate('Plans/view.html', [
        'view' => $plan->getPlans()
        ]);
    }
}

But it says ‘Message: ‘Undefined variable $plan’ !

When I use this instead it’s work !

'view' => $this->plan->getPlans()

Please note I’m using this code in a controller file ( twig ).

Thanks in advance.

How create relation one-to-one for doctrine entities in this case?

I want store meta data of products and categories in separated table i.e. creates tables:

meta_data (id, title, description, ...)
products (id, ..., meta_data_id)
categories (id, ..., meta_data_id)

I create entities with one-to-one relations:

class MetaData
{
    #[ORMId]
    #[ORMGeneratedValue]
    #[ORMColumn(type: Types::INTEGER, unique: true, options: ['unsigned' => true])]
    private int $id;

    ...
}

class Product
{
    #[ORMColumn(type: Types::INTEGER, unique: true, options: ['unsigned' => true])]
    #[ORMId]
    #[ORMGeneratedValue]
    private int $id;

    #[ORMOneToOne(targetEntity: MetaData::class, cascade: ['persist'], orphanRemoval: true)]
    #[ORMJoinColumn(name: 'meta_data_id', unique: true, onDelete: 'CASCADE')]
    private MetaData $metaData;

    ...
}

class Category
{
    #[ORMColumn(type: Types::INTEGER, unique: true, options: ['unsigned' => true])]
    #[ORMId]
    #[ORMGeneratedValue]
    private int $id;

    #[ORMOneToOne(targetEntity: MetaData::class, cascade: ['persist'], orphanRemoval: true)]
    #[ORMJoinColumn(name: 'meta_data_id', unique: true, onDelete: 'CASCADE')]
    private MetaData $metaData;

    ...
}

In this case, foreign keys adds to products and categories tables, but I want adds foreign keys to meta_data table to delete meta data at product/category deletion. How configure entities in this case?

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?