pass the search item into url

I am trying to learn react by practising a simple problem.
I followed the below youtube tutorial
https://www.youtube.com/watch?v=AsvybgZTryo&list=PLKhlp2qtUcSZiWKJTi5-5r6IRdHhxP9ZU&index=17

but when I search for an item the item is not passing properly in the api call

https://dummyjson.com/users/search?q=${searchTerm}

can you let me know what is the issue, providing the codse snippet and stackblitz below

useEffect(() => {
    const fetchUsers = () => {
      if (searchTerm.trim() === '') {
        setSuggestions([]);
        return;
      }
      fetch('https://dummyjson.com/users/search?q=${searchTerm}')
        .then((res) => {
          res.json();
        })
        .then((data) => setSuggestions(data))
        .catch((err) => {
          console.log('error', err);
        });
    };
    fetchUsers();
  }, [searchTerm]);

JS OAuth Client Library

I’m writing a small SPA using Vue.js and need to implement the OAuth Authorization Code Flow with PKCE.

I have been looking for a JS library that can help me accomplish this task but have not been able to find anything that actually works (many libraries have security vulnerabilities and will not build on recent Node versions). I’m using ADFS 2019 as authorization server, so there is no vendor-specific library available.

I’m looking for the following functionality:

  • Helper functions to make the initial authorizations request, handle the redirect and obtain access/ID/refresh tokens from the token endpoint (including PKCE code challenge)
  • Mechanism to store and retrieve tokens on the client
  • Helper function to exchange the refresh token for a new access token when needed (i.e. when the access token has expired)

A generic JS library would be great – but a component written specifically for Vue.js with an example implementation would be even better.

I figure this must be a very common requirement and that there must be something out there that fits the bill?

How to replace code before transpiling TypeScript code using Rollup. (How to transpile TypeScript code with Private Class Fields to ES5.)

I wont to do

I want to transpile TypeScript code with Private Class Fields to ES5 like when using TypeScript’s private identifier.

(I give up one or the other, it will be resolved soon. But I don’t want to give up.)

I did

I tried to replace code before transpiling TypeScript code using Rollup.

// ./src/index.mts
export class Foo {
  #foo = 0
  #bar = 'a'
  get foo() {
    return this.#foo
  }
  set foo(value) {
    this.#foo = value
  }
  get bar() {
    return this.#bar
  }
  set bar(value) {
    this.#bar = value
  }
}
// rollup.config.mjs
import replace from '@rollup/plugin-replace'
import typescript from '@rollup/plugin-typescript'
export default {
  input: './src/index.mts',
  output: {
    file: './dist/index.js',
    format: 'iife'
  },
  plugins: [
    replace({ // Assumption that ' #' and '.#' are not used in strings, regular expressions, etc.
      ' #': ' private _',
      '.#': '._',
      delimiters: ['', '']
    }),
    typescript({compilerOptions: {target: 'es5'}})
  ]
}

result

JavaScript code using WeakMap is output. Not appear to be replaced.

Changing from ' private _' to ' _', same result.

How can I solve what I want?

note

In JavaScript file, expected result is output.

So I see nothing wrong with Rollup replace plugin part.

// rollup.config.mjs
import replace from '@rollup/plugin-replace'
export default {
  input: './src/index.mjs',
  output: {
    file: './dist/index.js',
    format: 'iife'
  },
  plugins: [
    replace({ // Assumption that ' #' and '.#' are not used in strings, regular expressions, etc.
      ' #': ' _',
      '.#': '._',
      delimiters: ['', '']
    })
  ]
}
// ./src/index.mjs (same as ./src/index.mts)
export class Foo {
  #foo = 0
  #bar = 'a'
  get foo() {
    return this.#foo
  }
  set foo(value) {
    this.#foo = value
  }
  get bar() {
    return this.#bar
  }
  set bar(value) {
    this.#bar = value
  }
}

HTML5 Server-Sent Events: new EventSource as variable

I am working with ESP8266 moduls in Wifi-Station Mode. Each new ESP8266 module is assigned a different IP address by the wifi router. That’s why I need the Ip addresses as Javascript variable, but that doesn’t work:

    let ip = location.host;
    let str1 = 'http://';
    let str3 = '/see';
    
    let strIpAdress = str1+ip+str3;                  // doesn't work
    //let strIpAdress = 'http://192.168.178.47/sse'; // works!

    var source = new EventSource(strIpAdress); 
    console.log(strIpAdress);                        // http://192.168.178.47/sse

I have also tried in JavaScript String Object Format, but also without success.
Does a fixed IP address always have to be specified to prevent manipulation?
Is there a solution?

NavBar, Pathes and pages

// components/Navigation.tsx

import Link from "next/link"

const NavBar: React.FC = () => {
    return (
        <nav>
            <ul>
                <li>
                    <Link href="/catalog">Каталог</Link>
                </li>
                <li>
                    <Link href="/catalog?categoryId=3">Аксессуары</Link>
                </li>
                <li>
                    <Link href="/catalog?categoryId=14">Парфюм</Link>
                </li>
                <li>
                    <Link href="/catalog?categoryId=7">Уход</Link>
                </li>
                <li>
                    <Link href="/partners">Стать партнером</Link>
                </li>
            </ul>
        </nav>
    )
}

export default NavBar

This is my component, I wrote the navigation bar using Next and TS.
The navigation bar was fine until I noticed that the page was not reloading.
The essence of the problem: navigation proceeds until you click on the link with the query, that is, “Accessories”, “Perfume” and “Care”. After clicking, it naturally goes to this page, but after clicking on another one, only the path changes, and the content remains unchanged. And the page can be seen only after you refresh the page and a new search query goes to the path in the search input.

I have already tried a bunch of methods, and decided to gradually reduce the complexity of the code, in the end nothing worked and I simplified everything and simplified the code in the hope that I might miss some point and if I come to the basics. And now I have already simplified the code as much as possible, the chat gpt does not understand what is happening, just like me.

Expose a getter as a simple variable name, ‘detached’ from parent object (without using a `with` statement)

Note: This question is not about ‘regular’ JavaScript, or best practices. It’s about “hacking” the JavaScript language as part of a DSL. I’m working on an update to the DSL which is backwards-compatible with the previous approach, so I need to faithfully match all the existing behavior. Please interpret this question in that light.


Question:

Let’s say we have an object with a getter like this:

let obj = {
  get foo() {
    console.log('executed');
    return 1;
  }
};

And our task is to somehow “pull foo out of obj” so that instead of writing obj.foo we can just write foo.

This is possible using a with statement:

<script>
  with(obj) {
    // example of user-land code, which I have no control over:
    console.log(foo + 1); // logs 'executed' and then '2'
    let bar = 3; // `bar` is trapped inside the `with` block, which is the thing I'm trying to avoid
  }
</script>

However, this creates a block scope, which I want to avoid because it traps variable declarations like let bar = 3; inside the block scope.

Important: I have full control over the construction of the above code. I’m simply given the user-land code (like the above 2 lines) as a string, and I can do whatever I want with it, so long as I expose the obj getters, and ensure that top-level declarations like let bar = 3 aren’t “trapped”. The existing solution involves ‘parsing’ the user code for top-level declarations, and pulling them out of the with ‘manually’, but I’d like to get rid of the with statement.

Thoughts:

I’m fairly sure this is not possible without something quite heavy-handed (e.g. runtime AST parsing stuff). This ability for with to expose a getter as a ‘direct’ variable name seems unique. I figured I’d ask just in case there’s a god-tier JS dev here who knows some dark magic that I don’t.

I don’t think Proxy is useful here, since there is no handler for simple ‘references’ to the Proxy object itself – only properties of it, or function calls on it, etc.

I’m not sure how relevant it will be to this question, since I don’t understand many of the details, but:

13.11 With Statement:

The with statement adds an object Environment Record for a computed object to the lexical environment of the running execution context. It then executes a statement using this augmented lexical environment. Finally, it restores the original lexical environment.

8.1.1 Environment Records:

There are two primary kinds of Environment Record values used in this specification: declarative Environment Records and object Environment Records. Declarative Environment Records are used to define the effect of ECMAScript language syntactic elements such as FunctionDeclarations, VariableDeclarations, and Catch clauses that directly associate identifier bindings with ECMAScript language values. Object Environment Records are used to define the effect of ECMAScript elements such as WithStatement that associate identifier bindings with the properties of some object. Global Environment Records and function Environment Records are specializations that are used for specifically for Script global declarations and for top-level declarations within functions.

The only alternative pathway available to me here is to come up with a robust way to “export”

Paste event.clipboardData not pasting multiple files in Firefox

I noticed Paste event.clipboardData could only paste a single copied file from the clipboard in Firefox (130.0.1), but Chrome (129.0.6668.70) and Edge could paste multiple files just fine.

I couldn’t find if this was a known issue, and MDN Web Docs stated this feature as “Baseline widely available”. So, could this be a bug, intended behavior or other?

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text" placeholder="Paste/Ctrl+V files anywhere">
  <p>Pasted files counter: <span id="counter">0</span></p>
  <p id="files" style="white-space: pre-line;"></p>
</body>

<script>
  document.addEventListener('paste', event => {
    Array.from(event.clipboardData.files).map(file => {
      document.querySelector('#counter').innerText = parseInt(document.querySelector('#counter').innerText) + 1;
      document.querySelector('#files').innerHTML += `<b>File ${document.querySelector('#counter').innerText}:</b> ${file.name} <i>(${file.type})</i>n`;
    });
  })
</script>

</html>

How can I convert a .dxf file into .json using node js?

I’ve the values of a dxf file in to form of .json file and I’am trying to convert that .json file into a .dxf file using node js

I’ve tried using this code
export const generateDXF = (i, o) => {
let dxfContent = 0nSECTIONn2nHEADERn0nENDSECn0nSECTIONn2nTABLESn0nENDSECn0nSECTIONn2nBLOCKSn0nENDSECn;
dxfContent += 0nSECTIONn2nENTITIESn;

i?.entities?.forEach((entity) => {
if (entity.type === “LINE”) {
dxfContent += 0nLINEn8n${entity.layer}n; // Layer
dxfContent += 10n${entity?.vertices[0].x}n20n${entity?.vertices[0].y}n30n${entity?.vertices[0].z}n; // Start point
dxfContent += 11n${entity?.vertices[1].x}n21n${entity?.vertices[1].y}n31n${entity?.vertices[1].z}n; // End point
}
});

dxfContent += 0nENDSECn0nEOFn; // End of DXF file
fs.writeFileSync(o, dxfContent);

return dxfContent;
};

Why parseInt returns different values when apply to array.map

I write a simple function in javascript to parse an IP address string and returns an array of integers that represents four octets:

function parseIP(str) {
  octets = str.split('.');
  console.log(octets);
  return octets.map(parseInt);
}

console.log(parseIP("0.0.0.0"));

To my surprise, the function returns [0, Nan, 0, 0] instead of [0, 0, 0, 0]. Any idea why it behaves this way?

How to make a quiz with firestore?

I’m implementing a quiz database. If anyone knows how to do it with Firestore as the database and also using react and javascript let me know. P.S. I know how to make use firebase but not firestore exactly. If anyone has sample code that fits this let me know, I just want to see an example of how you use firestore.

I have not tried anything yet just wanting to see how other people use the database from firestore and make quizzes.

Issue with TonConnect UI React Package – “Operation Aborted” Error

I’m using the tonconnect/ui-react package to trigger wallet connections in my project. The wallet connection is established successfully, but after 5 to 10 minutes, I start seeing the following error:

[TON_CONNECT_SDK_ERROR] TonConnectError
Operation aborted
Error: Operation aborted
at abortController.signal.addEventListener.once (http://localhost:3000/static/js/bundle.js:64575:14)
at signal.addEventListener.once (http://localhost:3000/static/js/bundle.js:64414:108)

Initially, everything works fine, but after some time, this error pops up. It seems to be related to the connection aborting after a certain amount of inactivity.

Has anyone encountered this issue or knows what could be causing the connection to abort after a delay?
Please any one help me to resolve this?

Why are Chrome V8 garbage collects so slow?

I work on a web multiplayer FPS game, and some users report huge hitches that are always Major GCs.

Garbage Collect Time

In this case, the garbage collect is 415ms, which is absolutely unacceptable while playing a game.

The JS Heap isn’t incredibly large, and in Chrome’s Memory tab it seems like the largest things getting allocated are “compiled code,” is this a Chrome bug or is there a better way to test why the GC is taking so long?

Memory Allocations

get id of ‘ViewReport’ button on report viewer

I am using ReportViewer Control in our MVC application.
The issue is when I click on ‘View Report’ button, the report gets shrink every time i click on button.
enter image description here

I am trying to get id of the View Report button which is present on report viewer.

I have tried multiple ways to get id of View Report button Here are my few attempts

const submitButton = document.querySelector("button[type='submit'][value='View Report']");

Please let me know how can we access the id of ‘View Report’

How do I get dates/times to display properly in production?

I’m trying to create an online booking sort of thing. I have this function set up to retrieve the available dates/times from my database, then compare them against times from my Google calendar using the google calendar api to display only the times that don’t conflict with any events marked busy. Everything works fine on my development server, but as soon as I upload to vercel for production, the times are off and therefore not properly being cross referenced with my Google calendar for conflicts. How can I get my production server to display the times correctly? I have tried using libraries like date-fns and specifying the timezone, but it still produced the same result in production. I’m using next.js so this is a server action being called and the result is displayed in my client component.

This is what my function looks like:

export const getAvailableAppointments = async (rmtLocationId, duration) => {
  const db = await getDatabase();
  const appointmentsCollection = db.collection("appointments");

  // Convert duration to an integer
  const durationMinutes = parseInt(duration, 10);

  // Fetch appointments with the given rmtLocationId and status 'available'
  const appointments = await appointmentsCollection
    .find({
      RMTLocationId: new ObjectId(rmtLocationId),
      status: "available",
    })
    .toArray();

  const availableTimes = [];

  appointments.forEach((appointment) => {
    const startTime = new Date(
      `${appointment.appointmentDate}T${appointment.appointmentStartTime}`
    );
    const endTime = new Date(
      `${appointment.appointmentDate}T${appointment.appointmentEndTime}`
    );

    let currentTime = new Date(startTime);

    while (currentTime <= endTime) {
      const nextTime = new Date(currentTime);
      nextTime.setMinutes(currentTime.getMinutes() + durationMinutes);

      if (nextTime <= endTime) {
        availableTimes.push({
          date: appointment.appointmentDate,
          startTime: currentTime.toTimeString().slice(0, 5), // Format as HH:MM
          endTime: nextTime.toTimeString().slice(0, 5), // Format as HH:MM
        });
      }

      currentTime.setMinutes(currentTime.getMinutes() + 30); // Increment by 30 minutes
    }
  });

  console.log("Available times:", availableTimes);

  // Fetch busy times from Google Calendar
  const now = new Date();
  const oneMonthLater = new Date();
  oneMonthLater.setMonth(now.getMonth() + 3);

  const busyTimes = await calendar.freebusy.query({
    requestBody: {
      timeMin: now.toISOString(),
      timeMax: oneMonthLater.toISOString(),
      items: [{ id: GOOGLE_CALENDAR_ID }],
    },
  });

  const busyPeriods = busyTimes.data.calendars[GOOGLE_CALENDAR_ID].busy;

  // Convert busy times from UTC to local timezone and format them
  const localBusyPeriods = busyPeriods.map((period) => {
    const start = new Date(period.start);
    const end = new Date(period.end);

    // Add 30-minute buffer before and after the busy period
    start.setMinutes(start.getMinutes() - 30);
    end.setMinutes(end.getMinutes() + 30);

    return {
      date: start.toISOString().split("T")[0], // Extract date in YYYY-MM-DD format
      startTime: start.toTimeString().slice(0, 5), // Format as HH:MM
      endTime: end.toTimeString().slice(0, 5), // Format as HH:MM
    };
  });

  console.log("Busy times (local with buffer):", localBusyPeriods);

  // Filter out conflicting times
  const filteredAvailableTimes = availableTimes.filter((available) => {
    return !localBusyPeriods.some((busy) => {
      return (
        available.date === busy.date &&
        ((available.startTime >= busy.startTime &&
          available.startTime < busy.endTime) ||
          (available.endTime > busy.startTime &&
            available.endTime <= busy.endTime) ||
          (available.startTime <= busy.startTime &&
            available.endTime >= busy.endTime))
      );
    });
  });

  console.log("Filtered available times:", filteredAvailableTimes);

  // Filter out dates that are not greater than today
  const today = new Date().toISOString().split("T")[0];
  const futureAvailableTimes = filteredAvailableTimes.filter(
    (available) => available.date > today
  );

  console.log("Future available times:", futureAvailableTimes);

  // Sort the results by date
  const sortedAvailableTimes = futureAvailableTimes.sort(
    (a, b) => new Date(a.date) - new Date(b.date)
  );

  console.log("Sorted available times:", sortedAvailableTimes);

  return sortedAvailableTimes;
};

This is an example of what the available times look like:

{ date: '2024-09-30', startTime: '13:00', endTime: '14:30' },

and this is an example of what the busy times from google calendar api look like after being formatted:

{ date: '2024-09-27', startTime: '12:30', endTime: '15:00' },

This is what my client component looks like:

"use client";

import React, { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { getAvailableAppointments, bookAppointment } from "@/app/_actions";
import { date } from "zod";

function BookMassageForm({ rmtSetup, user, healthHistory }) {
  const router = useRouter();
  const [currentStep, setCurrentStep] = useState(1);
  const [appointmentTimes, setAppointmentTimes] = useState([]);
  const [currentPage, setCurrentPage] = useState(0);
  const [selectedAppointment, setSelectedAppointment] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [formData, setFormData] = useState({
    location: "",
    RMTLocationId: "",
    duration: "",
    appointmentTime: "",
    workplace: "",
    appointmentDate: "",
  });

  const handleInputChange = async (event) => {
    const { name, value } = event.target;
    setFormData((prevData) => ({ ...prevData, [name]: value }));

    if (name === "location") {
      const selectedSetup = rmtSetup.find(
        (setup) => setup.formattedFormData.address.streetAddress === value
      );
      if (selectedSetup) {
        setFormData((prevData) => ({
          ...prevData,
          RMTLocationId: selectedSetup._id,
        }));
      }
    }
  };

  useEffect(() => {
    const fetchAppointments = async () => {
      if (formData.RMTLocationId && formData.duration) {
        setLoading(true);
        setError(null);
        try {
          console.log(
            `Fetching appointments for RMTLocationId: ${formData.RMTLocationId}, duration: ${formData.duration}`
          );
          const times = await getAvailableAppointments(
            formData.RMTLocationId,
            parseInt(formData.duration),
            process.env.NEXT_PUBLIC_TIMEZONE
          );

          console.log("Fetched appointment times:", times);

          // Sort the times array by date
          const sortedTimes = times.sort(
            (a, b) => new Date(a.date) - new Date(b.date)
          );

          console.log("Sorted appointment times:", sortedTimes);

          // Group appointments by date
          const groupedTimes = sortedTimes.reduce((acc, appointment) => {
            const { date, startTime, endTime } = appointment;
            if (!acc[date]) {
              acc[date] = [];
            }
            acc[date].push({ startTime, endTime });
            return acc;
          }, {});

          console.log("Grouped appointment times:", groupedTimes);

          // Convert grouped times back to array format and format the dates and times
          const groupedAppointments = Object.entries(groupedTimes).map(
            ([date, times]) => {
              const formattedDate = new Date(
                `${date}T00:00:00`
              ).toLocaleDateString("en-US", {
                year: "numeric",
                month: "long",
                day: "numeric",
              });

              const formattedTimes = times.map(({ startTime, endTime }) => {
                const start = new Date(`${date}T${startTime}`);
                const end = new Date(`${date}T${endTime}`);
                return `${start.toLocaleTimeString("en-US", {
                  hour: "numeric",
                  minute: "numeric",
                  hour12: true,
                })} - ${end.toLocaleTimeString("en-US", {
                  hour: "numeric",
                  minute: "numeric",
                  hour12: true,
                })}`;
              });

              return {
                date: formattedDate,
                times: formattedTimes.sort((a, b) => a.localeCompare(b)),
              };
            }
          );

          console.log("Grouped appointments:", groupedAppointments);

          setAppointmentTimes(groupedAppointments);
        } catch (error) {
          console.error("Error fetching appointment times:", error);
          setError("Failed to fetch appointment times. Please try again.");
        } finally {
          setLoading(false);
        }
      }
    };

    fetchAppointments();
  }, [formData.RMTLocationId, formData.duration]);

  const renderAppointments = () => {
    if (loading) {
      return <p>Loading available appointments...</p>;
    }

    if (error) {
      return <p className="text-red-500">{error}</p>;
    }

    if (!appointmentTimes || appointmentTimes.length === 0) {
      return (
        <p>
          No available appointments found. Please try a different location or
          duration.
        </p>
      );
    }

    const dates = appointmentTimes.slice(
      currentPage * 5,
      (currentPage + 1) * 5
    );

    return (
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
        {dates.map((dateGroup, index) => (
          <div key={index} className="bg-white shadow-md rounded-lg p-4">
            <h4 className="text-lg font-semibold mb-2">{dateGroup.date}</h4>
            <ul className="space-y-2">
              {dateGroup.times.map((time, idx) => {
                const isSelected =
                  selectedAppointment &&
                  selectedAppointment.date === dateGroup.date &&
                  selectedAppointment.time === time;

                return (
                  <li
                    key={idx}
                    className={`cursor-pointer p-2 rounded transition-colors ${
                      isSelected
                        ? "bg-blue-200 text-blue-800"
                        : "text-gray-700 hover:bg-gray-100"
                    }`}
                    onClick={() => {
                      setSelectedAppointment({
                        date: dateGroup.date,
                        time: time,
                      });
                      setFormData({
                        ...formData,
                        appointmentTime: time,
                        appointmentDate: dateGroup.date,
                      });
                    }}
                  >
                    {time}
                  </li>
                );
              })}
            </ul>
          </div>
        ))}
      </div>
    );
  };

  const nextStep = () => {
    setCurrentStep((prevStep) => prevStep + 1);
  };

  const prevStep = () => {
    setCurrentStep((prevStep) => prevStep - 1);
  };

  return (
    <form
      action={async () => {
        console.log("Submitting appointment:", formData);
        await bookAppointment({
          location: formData.location,
          duration: formData.duration,
          appointmentTime: formData.appointmentTime,
          workplace: formData.workplace,
          appointmentDate: formData.appointmentDate,
          RMTLocationId: formData.RMTLocationId,
          timezone: process.env.NEXT_PUBLIC_TIMEZONE,
        });
      }}
      className="max-w-4xl mx-auto px-4 py-8 space-y-8"
    >
      {currentStep === 1 && (
        <div className="space-y-4">
          <h1 className="text-2xl sm:text-3xl">
            Select the location where you would like to book a massage:
          </h1>
          <select
            name="location"
            value={formData.location}
            onChange={handleInputChange}
            required
            className="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-gray-800 focus:border-gray-800"
          >
            <option value="" disabled>
              Select a location
            </option>
            {rmtSetup.map((setup, index) => (
              <option
                key={index}
                value={setup.formattedFormData.address.streetAddress}
              >
                {setup.formattedFormData.address.locationName ||
                  setup.formattedFormData.address.streetAddress}
              </option>
            ))}
          </select>
          <button
            className="w-full sm:w-auto px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-800 focus:ring-offset-2"
            type="button"
            onClick={nextStep}
            disabled={!formData.location}
          >
            Next
          </button>
        </div>
      )}

      {currentStep === 2 && (
        <div className="space-y-4">
          <h1 className="text-2xl sm:text-3xl">
            What length of massage session would you like to book?
          </h1>
          <select
            name="duration"
            value={formData.duration}
            onChange={handleInputChange}
            className="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-gray-800 focus:border-gray-800"
          >
            <option value="" disabled>
              Select a service
            </option>
            {rmtSetup
              .find(
                (setup) =>
                  setup.formattedFormData.address.streetAddress ===
                  formData.location
              )
              ?.formattedFormData?.massageServices.map((service, index) => (
                <option key={index} value={service.duration}>
                  {service.duration} minute {service.service} - ${service.price}{" "}
                  {service.plusHst ? "+HST" : ""}
                </option>
              ))}
          </select>
          <div className="flex space-x-4">
            <button
              className="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
              type="button"
              onClick={prevStep}
            >
              Back
            </button>
            <button
              className="px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-800 focus:ring-offset-2"
              type="button"
              onClick={nextStep}
              disabled={!formData.duration}
            >
              Next
            </button>
          </div>
        </div>
      )}

      {currentStep === 3 && (
        <div className="space-y-4">
          <h1 className="text-2xl sm:text-3xl">
            Select a date and time for your massage:
          </h1>
          {renderAppointments()}
          {appointmentTimes.length > 0 && (
            <div className="flex justify-between">
              <button
                type="button"
                onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 0))}
                disabled={currentPage === 0}
                className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50"
              >
                Previous
              </button>
              <button
                type="button"
                onClick={() => setCurrentPage((prev) => prev + 1)}
                disabled={(currentPage + 1) * 5 >= appointmentTimes.length}
                className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50"
              >
                Next
              </button>
            </div>
          )}
          <div className="flex space-x-4">
            <button
              className="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
              type="button"
              onClick={prevStep}
            >
              Back
            </button>
            <button
              className="px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-800 focus:ring-offset-2"
              type="button"
              onClick={nextStep}
              disabled={!selectedAppointment}
            >
              Next
            </button>
          </div>
        </div>
      )}

      {currentStep === 4 && (
        <div className="space-y-4">
          <h1 className="text-2xl sm:text-3xl">
            Does the following information look correct?
          </h1>
          <div className="bg-white shadow-md rounded-lg p-4 space-y-2">
            <p>
              <strong>Location:</strong> {formData.location}
            </p>
            <p>
              <strong>Duration:</strong> {formData.duration} minutes
            </p>
            <p>
              <strong>Date:</strong> {formatDate(formData.appointmentDate)}
            </p>
            <p>
              <strong>Time:</strong> {formatTime(formData.appointmentTime)}
            </p>
          </div>
          <div className="flex space-x-4">
            <button
              className="px-4 py-2 bg-gray-800 text-white rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-800 focus:ring-offset-2"
              type="submit"
            >
              Yes, Book Appointment
            </button>
            <button
              className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
              type="button"
              onClick={prevStep}
            >
              No, Go Back
            </button>
          </div>
        </div>
      )}
    </form>
  );
}

export default BookMassageForm;

how to stop the FFmpeg process programmatically without using Ctrl + C?

const shell = require("shelljs");

const processShell = shell.exec(
  `ffmpeg -i "https://pull-hls-l1-va01.tiktokcdn.com/game/stream-2998227837016342624_or4/playlist.m3u8?expire=1728613179&session_id=000-20240927021937E401758EF5D00A0AC325&sign=7aeab541c3ef8072d52a9fe799f8692b" -movflags use_metadata_tags -map_metadata 0 -metadata title="Chill chill kiếm kèo Warthunder" -metadata artist="bacgaucam" -metadata year="2024" -c copy "downloads/bacgaucam-927202491939.mp4" -n -stats -hide_banner -loglevel error`,
  { async: true }
);

setTimeout(() => {
  // processShell.kill();
  process.exit();
}, 20000);

my video only works when I use Ctrl+C to stop it. I’ve tried using process.exit(), .kill(pid, "SIGINT"), and the .kill() method from the shell.exec() reference, but none of them work

can anyone help? Thanks!