Dec 2024 – PhpStorm uploads to server – but outside of Public_Html directory — I tried everything

for the last 4 days using PhpStorm, Dec 2024 version, I have played with deplyment configuration, trying different (a) root path (b) mapping deployment path … but sadly nothing works; however, when I log into the remote server I can see that PhpStorm did create a directory outside of the Public_html directory, and the files were uploaded there indeed.

Problem is that …

for example:
I want files uploaded to xyz.com/welcome
I used the”/” as root path
and /welcome as the deployment path

However:
files get uploaded to
/home/user/
and not to
/home/user/public_html/welcome

I also checked with YouTube but the vids on this subject are over 7-9 years old, and I just recently downloaded PhpStorm some days back. So their screenshots are not the same as mine, sadly.

I never had any issues with FTP software before, but this feature is so important that I do not yet understand why there is no illustrated written examples – or even updated YouTube videos on this.

I just subscribed to PhpStorm — so I am new at this — and I’m not sure if the software has a bug in it or if I am dumber than dumb ….. please help me on this — thanks!!

I tried different deployment configurations
I tried watching youtube videos
I tried googling the answer

There is really nothing current on this issue anywhere

So, I come to StackOverflow to seek answer from the Pros

Please

listening hook on course module or activity or resource added on moodle

I need to trigger a custom action or send an HTTP request to my API whenever a course module, activity, or resource is added in Moodle 4.5 The API will handle indexing for the added module.

Is there a way to add a hook or listen to an existing event for this scenario in Moodle?

Any guidance, examples, or references to the relevant APIs or documentation would be greatly appreciated!

Thanks in advance.

Detect if user manually refreshes a php-based web page [closed]

I have an php-based web page which auto-refreshes, and, I need to detect whether the user has manually refreshed the page. This is because I am using audio embedded within an iframe to sound alerts and for that to work reliably with Chrome, web page interaction by the user is required (to enable the audio to sound). The interaction is initially satisfied by the user pressing a form button to enables audio alerts (in Chrome). However, if the user manually refreshes the web page later, the audio is disabled (unless the user subsequently clicks the web page again). Am working on finding a solution.

Am still experimenting and looking for solutions…..

The session is not sent from the server

then upon authorization, we receive a session and go through authorization. If I use ip 192.168.0.100:8080 both when creating the server and when redirecting, then we receive user data, but we do not send anything in response. I mean something related to /profile. It’s as if it can’t handle IP requests.

main.go

package main

import (
    global "DiscordBotGo/Global"
    "DiscordBotGo/auth" // Добавлен пакет авторизации
    "DiscordBotGo/coins"
    "DiscordBotGo/utils"
    "fmt"
    "log"
    "net/http"

    "github.com/bwmarrin/discordgo"
    "github.com/gorilla/mux"
)

var serverip = "localhost:8080"

func main() {

    // Проверяем инициализацию сессии перед запуском
    if global.BotSession == nil {
        var err error
        global.BotSession, err = discordgo.New("Bot тут мой токен")
        if err != nil {
            log.Fatal("Ошибка при создании сессии Discord:", err)
        }
    }

    // Обработчик для статических файлов
    http.FileServer(http.Dir("./"))                                                               // Указываем корневую директорию проекта
    http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./css"))))           // Разрешаем загрузку файлов из папки /css                                         // Разрешаем загрузку файлов из папки /js
    http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets/")))) // Для изображений
    http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./js/"))))

    // Открываем сессию
    err := global.BotSession.Open()
    if err != nil {
        fmt.Println("Ошибка при подключении к Discord:", err)
        return
    }
    defer global.BotSession.Close()

    coins.StartAutoReward(global.BotSession)

    // Добавляем маршруты для авторизации
    http.HandleFunc("/login", auth.LoginHandler)
    http.HandleFunc("/callback", auth.CallbackHandler)
    http.HandleFunc("/profile", auth.ProfileHandler)
    http.HandleFunc("/logout", auth.LogoutHandler)
    http.HandleFunc("/distribute", utils.DistributeTeams)
    http.HandleFunc("/create-product", utils.CreateProductHandler)
    http.HandleFunc("/api/products/{id}", utils.GetProductByIDHandler)
    http.HandleFunc("/check-auth", auth.CheckAuthHandler)
    http.HandleFunc("/api/products/", utils.GetProductByIDHandler)
    http.HandleFunc("/check-role-for-tournament", checkRoleForTournamentHandler)
    http.HandleFunc("/roulette-images/", utils.GetRouletteImagesHandler)

    // Обрабатываем POST запрос на /send-links
    http.HandleFunc("/send-links", utils.SendMatchLinks)
    http.HandleFunc("/buy-product", utils.BuyProductHandler)
    http.HandleFunc("/api/user-roles", utils.UserRolesHandler)
    http.HandleFunc("/profile-redirect", utils.ProfileRedirectHandler)
    http.HandleFunc("/api/products", utils.GetProductsByCategoryHandler)
    http.HandleFunc("/api/coins", utils.CoinsHandler)
    http.HandleFunc("/save-news", utils.SaveNewsHandler)
    http.Handle("/images/", http.StripPrefix("/images/", http.FileServer(http.Dir("images"))))
    http.HandleFunc("/get-news", utils.GetNewsHandler)
    http.HandleFunc("/delete-news", utils.DeleteNewsHandler)

    r := mux.NewRouter()
    r.HandleFunc("/api/products/{id}", utils.GetProductByIDHandler).Methods("GET")
    // Дальше идёт ваш HTTP-сервер
    http.HandleFunc("/", serveHTML)
    http.HandleFunc("/command", utils.HandleCommand)

    fmt.Println("Сервер запущен на", serverip)
    err = http.ListenAndServe(serverip, nil)
    if err != nil {
        fmt.Println("Ошибка при запуске HTTP-сервера:", err)
    }
}

func serveHTML(w http.ResponseWriter, r *http.Request) {
    // Если путь запроса "/" (корень), возвращаем index.html
    if r.URL.Path == "/" {
        http.ServeFile(w, r, "index.html")
        return
    }

    // Если запрашивается другой файл, пытаемся его найти
    http.FileServer(http.Dir("./")).ServeHTTP(w, r)
}

func checkRoleForTournamentHandler(w http.ResponseWriter, r *http.Request) {
    userID := auth.GetUserIdFromSessionOrHeader(r)
    if userID == "" {
        http.Error(w, "Пользователь не аутентифицирован", http.StatusUnauthorized)
        return
    }

    // Получаем роли пользователя из Discord
    userRoles := auth.GetUserRolesFromDiscord(userID)
    if userRoles == nil {
        http.Error(w, "Ошибка при получении ролей пользователя", http.StatusInternalServerError)
        return
    }

    // Определяем роли администраторов
    adminRoles := []string{"1318188643944501289", "1318188870415679508", "1318189013823389711"}

    // Проверяем, есть ли у пользователя одна из ролей админа
    isAdmin := false
    for _, role := range userRoles {
        for _, adminRole := range adminRoles {
            if role == adminRole {
                isAdmin = true
                break
            }
        }
        if isAdmin {
            break
        }
    }

    // Если пользователь является админом, предоставляем доступ
    if isAdmin {
        w.WriteHeader(http.StatusOK)
    } else {
        http.Error(w, "У вас нет прав доступа к этой странице", http.StatusForbidden)
    }
}

auth.go

package auth

import (
    global "DiscordBotGo/Global"
    "DiscordBotGo/db"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"

    "github.com/gorilla/sessions"
    "golang.org/x/oauth2"
)

// Определяем OAuth2 Endpoint для Discord
var discordEndpoint = oauth2.Endpoint{
    AuthURL:  "https://discord.com/api/oauth2/authorize",
    TokenURL: "https://discord.com/api/oauth2/token",
}

// Переменные для OAuth2
var (
    oauthConfig = &oauth2.Config{
        ClientID:     "тут мой клиент id",              // Замените на ваш Client ID
        ClientSecret: "тут секретный ключ", // Замените на ваш Client Secret
        RedirectURL:  "http://localhost/callback",        // URL перенаправления после авторизации
        Scopes:       []string{"identify"},               // Указываем необходимые права
        Endpoint:     discordEndpoint,                    // Используем Discord OAuth2 Endpoint
    }
)

var SessionStore = sessions.NewCookieStore([]byte("a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0")) // Хранилище сессий

// Инициализация базы данных
func init() {
    if err := db.InitDB(); err != nil {
        log.Fatalf("Ошибка при инициализации базы данных: %v", err)
    }
}

// LoginHandler — перенаправляет пользователя на Discord для авторизации
func LoginHandler(w http.ResponseWriter, r *http.Request) {
    url := oauthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline) // Генерируем URL для авторизации
    http.Redirect(w, r, url, http.StatusTemporaryRedirect)            // Перенаправляем на Discord
}

// CallbackHandler — обработчик для обработки ответа Discord
func CallbackHandler(w http.ResponseWriter, r *http.Request) {
    code := r.URL.Query().Get("code")
    if code == "" {
        http.Error(w, "Ошибка: нет кода в запросе", http.StatusBadRequest)
        return
    }

    token, err := oauthConfig.Exchange(r.Context(), code)
    if err != nil {
        http.Error(w, "Не удалось получить токен: "+err.Error(), http.StatusInternalServerError)
        return
    }

    client := oauthConfig.Client(r.Context(), token)

    resp, err := client.Get("https://discord.com/api/users/@me")
    if err != nil {
        http.Error(w, "Ошибка получения данных пользователя: "+err.Error(), http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()

    var userData struct {
        ID            string `json:"id"`
        Username      string `json:"username"`
        Discriminator string `json:"discriminator"`
        Avatar        string `json:"avatar"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil {
        http.Error(w, "Ошибка декодирования данных пользователя: "+err.Error(), http.StatusInternalServerError)
        return
    }

    // Генерация URL аватара
    var avatarURL string
    if userData.Avatar != "" {
        avatarURL = fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s.png", userData.ID, userData.Avatar)
    } else {
        discriminator, err := strconv.Atoi(userData.Discriminator)
        if err != nil {
            http.Error(w, "Ошибка обработки данных пользователя: "+err.Error(), http.StatusInternalServerError)
            return
        }
        defaultAvatarIndex := discriminator % 5
        avatarURL = fmt.Sprintf("https://cdn.discordapp.com/embed/avatars/%d.png", defaultAvatarIndex)
    }

    // Логируем URL аватара для отладки
    log.Printf("Avatar URL: %s", avatarURL)

    // Сохраняем данные пользователя и аватар в сессии
    session, _ := SessionStore.Get(r, "discord-session")
    session.Values["userID"] = userData.ID
    session.Values["username"] = userData.Username
    session.Values["avatar"] = avatarURL
    err = session.Save(r, w)
    log.Printf("Session saved with userID: %s", userData.ID)
    if err != nil {
        log.Printf("Ошибка сохранения сессии: %v", err)
        http.Error(w, "Ошибка сохранения сессии", http.StatusInternalServerError)
        return
    }

    // Добавляем пользователя в базу данных
    err = db.AddUser(userData.ID, userData.Username)
    if err != nil {
        http.Error(w, "Ошибка при добавлении пользователя в базу данных: "+err.Error(), http.StatusInternalServerError)
        return
    }

    // Перенаправляем на страницу профиля
    http.Redirect(w, r, "/index.html", http.StatusSeeOther)

}

func AddRoleToUser(userID string, roleID int64) error {
    guildID := global.ServerID // Замените на ID вашего сервера

    session := global.BotSession
    if session == nil {
        return fmt.Errorf("сессия бота не инициализирована")
    }

    // Преобразуем roleID в строку, так как GuildMemberRoleAdd ожидает строку
    err := session.GuildMemberRoleAdd(guildID, userID, strconv.FormatInt(roleID, 10))
    if err != nil {
        return fmt.Errorf("не удалось выдать роль: %v", err)
    }

    log.Printf("Роль %d успешно выдана пользователю %s", roleID, userID)
    return nil
}

// ProfileHandler — возвращает данные профиля в формате JSON

func ProfileHandler(w http.ResponseWriter, r *http.Request) {

    session, _ := SessionStore.Get(r, "discord-session")
    userID, ok := session.Values["userID"].(string)
    if userID, ok := session.Values["userID"].(string); ok {
        log.Printf("Session userID: %s", userID)
    } else {
        log.Printf("Session userID: not found")
    }
    if !ok {
        http.Error(w, "Не авторизован", http.StatusUnauthorized)
        return
    }

    // Получаем данные пользователя (nickname, coins, status) из базы
    nickname, coins, status, err := db.GetUser(userID)
    if err != nil {
        http.Error(w, "Ошибка при получении данных пользователя: "+err.Error(), http.StatusInternalServerError)
        return
    }

    // Получаем аватар пользователя из сессии
    avatar, ok := session.Values["avatar"].(string)
    if !ok || avatar == "" {
        avatar = "/default-avatar.png" // Если аватар не найден, используем дефолтный
    }

    // Получаем роли пользователя из Discord
    userRoles := GetUserRolesFromDiscord(userID)

    // Проверяем, имеет ли пользователь роль VIP
    vipRole := "1320294095020621844" // ID VIP роли
    isVIP := false
    for _, role := range userRoles {
        if role == vipRole {
            isVIP = true
            break
        }
    }

    // Создаем объект с данными профиля для отправки в JSON
    profileData := map[string]interface{}{
        "username": nickname,
        "avatar":   avatar,
        "coins":    coins,
        "status":   status,
        "roles":    userRoles,
        "isVIP":    isVIP, // Добавляем поле VIP
    }

    // Устанавливаем заголовок ответа для JSON
    w.Header().Set("Content-Type", "application/json")

    // Отправляем профиль в формате JSON
    err = json.NewEncoder(w).Encode(profileData)
    if err != nil {
        http.Error(w, "Ошибка отправки данных профиля: "+err.Error(), http.StatusInternalServerError)
    }
}

func GetUserRolesFromDiscord(userID string) []string {
    guildID := global.ServerID // ID вашего сервера
    session := global.BotSession
    if session == nil {
        return nil
    }

    member, err := session.GuildMember(guildID, userID)
    if err != nil {
        log.Printf("Ошибка при получении ролей пользователя: %v", err)
        return nil
    }

    var roles []string
    for _, roleID := range member.Roles {
        roles = append(roles, roleID)
    }
    return roles
}

// LogoutHandler — удаляет данные сессии пользователя
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
    // Удаляем сессию
    session, _ := SessionStore.Get(r, "discord-session")
    session.Options = &sessions.Options{
        Path:     "/",
        MaxAge:   86400 * 30, // 30 дней
        HttpOnly: true,
        Secure:   true,                 // Включите, если используете HTTPS
        SameSite: http.SameSiteLaxMode, // Рекомендуется для защиты от CSRF атак
    }

    session.Values = make(map[interface{}]interface{}) // Полностью очищаем значения
    session.Save(r, w)

    // Перенаправляем на главную страницу
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

func CheckAuthHandler(w http.ResponseWriter, r *http.Request) {
    session, _ := SessionStore.Get(r, "discord-session")
    if _, ok := session.Values["userID"].(string); ok {
        w.WriteHeader(http.StatusOK)
    } else {
        w.WriteHeader(http.StatusUnauthorized)
    }
}

func GetUserIdFromSessionOrHeader(r *http.Request) string {
    session, _ := SessionStore.Get(r, "discord-session")
    if userID, ok := session.Values["userID"].(string); ok {
        return userID
    }

    userIDFromHeader := r.Header.Get("X-User-ID")
    if userIDFromHeader != "" {
        return userIDFromHeader
    }

    return ""
}

index.js

document.addEventListener('DOMContentLoaded', () => {
    document.getElementById('news-section').classList.remove('loading');

    // Сначала скрываем окно авторизации
    document.getElementById('unauthorized-message').classList.remove('show');

    function preloadPage(url) {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.send();
    }

    // Предзагрузка страниц при загрузке текущей страницы
    window.addEventListener('load', function() {
        ['profile.html', 'shop.html'].forEach(url => {
            const link = document.createElement('link');
            link.rel = 'prefetch';
            link.href = url;
            document.head.appendChild(link);
        });
    });

    document.getElementById('news-section').classList.remove('blurred');

    fetch('/profile', {
        method: 'GET',
        credentials: 'include'
    })

    .then(response => {
            if (!response.ok) {
                // Показываем окно только если пользователь не авторизован
                document.getElementById('blur-overlay').classList.add('blurred');
                document.getElementById('nav-links').classList.add('hidden');
                document.getElementById('unauthorized-message').classList.add('show');
                throw new Error('Пользователь не авторизован');
            }
            return response.json();
        })
        .then(data => {
            // Логирование полученных данных пользователя
            console.log('Полученные данные пользователя:', data);

            // Проверка ключей данных
            if (!data.username) {
                console.error('Отсутствует ключ "username" в данных пользователя');
            } else {
                console.log(`Имя пользователя: ${data.username}`);
            }

            if (!data.roles || !Array.isArray(data.roles)) {
                console.error('Отсутствует ключ "roles" или он не является массивом');
            } else {
                console.log(`Роли пользователя: ${data.roles.join(', ')}`);
            }

            if (data.isVIP === undefined) {
                console.error('Отсутствует ключ "isVIP" в данных пользователя');
            } else {
                console.log(`Статус VIP: ${data.isVIP ? 'Да' : 'Нет'}`);
            }

            // Убираем классы, когда пользователь авторизован
            document.getElementById('blur-overlay').classList.remove('blurred');
            document.getElementById('nav-links').classList.remove('hidden');
            document.getElementById('unauthorized-message').classList.remove('show');

            const newsSection = document.getElementById('news-section');
            const userRoles = data.roles || [];
            const adminRoles = ["1318188643944501289", "1318188870415679508", "1318189013823389711"];
            const isAdmin = userRoles.some(role => adminRoles.includes(role));

            document.getElementById('tournaments-link').style.display = isAdmin ? 'inline' : 'none';

            // Функция для обновления новостей
            function updateNews() {
                fetch('/get-news')
                    .then(response => response.json())
                    .then(news => {
                        console.log('Загруженные новости:', news);

                        newsSection.innerHTML = '';
                        if (news.length === 0) {
                            console.log('Новости не загружены или пусты');
                        } else {
                            news.forEach((newsData) => {
                                const newsBlock = document.createElement('div');
                                newsBlock.classList.add('news-block');

                                const deleteButton = document.createElement('span');
                                deleteButton.textContent = '❌';
                                deleteButton.classList.add('delete-btn');

                                if (isAdmin) {
                                    deleteButton.style.display = 'block';
                                    deleteButton.onclick = () => {
                                        fetch(`/delete-news?id=${newsData.id}`, { method: 'DELETE' })
                                            .then(response => {
                                                if (response.ok) {
                                                    newsSection.removeChild(newsBlock);
                                                    console.log('Новость успешно удалена');
                                                } else {
                                                    console.error('Ошибка при удалении новости');
                                                }
                                            })
                                            .catch(err => console.error('Ошибка:', err));
                                    };
                                } else {
                                    deleteButton.style.display = 'none';
                                }

                                newsBlock.innerHTML = `
                  <img src="${newsData.image}" alt="News image" loading="lazy">
                  <h3>${newsData.title}</h3>
                  <p>${newsData.text}</p>
                `;

                                newsBlock.appendChild(deleteButton);
                                newsSection.insertBefore(newsBlock, newsSection.firstChild);
                            });
                        }
                    })
                    .catch(err => console.error('Ошибка загрузки новостей:', err));
            }

            // Первоначальная загрузка новостей
            updateNews();

            // Интервал для повторной загрузки новостей каждые 10 секунд
            setInterval(updateNews, 10000);
        })
        .catch(err => {
            console.error('Ошибка:', err.message);
        });

    // Функция для показа окна авторизации
    const authPrompt = document.getElementById('unauthorized-message');

    function showAuthPrompt() {
        authPrompt.classList.add('show');
    }


});

I used different methods and opened ports. And used CORS settings.

On page load a button must reflect its database state. How to do?

A page includes buttons that have state. When the page loads, how to query the database during the onLoad event so the actual button state is reflected in the button as drawn?

I expected this to work:

window.addEventListener("DOMContentLoaded", ready);
function ready(event) {
   document.getElementById(THE_BUTTON).click();
}

Any suggestions welcome.
Ron

How can i optimize these repetitive if with queryselectors inside?

i need to simplify these lines of code of react, i see is a bit repetitive, but i dont know if theres any way to improve this

Any idea will be received!!!

useEffect(() => {
    document.addEventListener("scroll", () => {
      // Prices
      if(document.querySelector("#prices").getBoundingClientRect().top == 78){
        document.querySelector(".pricesArr").style.opacity = 1
        document.querySelector(".pricesArr").classList.add("pricesArrAnim")
      }
      else if(document.querySelector("#prices").getBoundingClientRect().top > 660){
        document.querySelector(".pricesArr").style.opacity = 0
        document.querySelector(".pricesArr").classList.remove("pricesArrAnim")
      }
      // Contact
      if(document.querySelector("#contact").getBoundingClientRect().top == 78){
        document.querySelector(".contact").style.opacity = 1
        document.querySelector(".contact").classList.add("contactAnim")
      }
      else if(document.querySelector("#contact").getBoundingClientRect().top > 660){
        document.querySelector(".contact").style.opacity = 0
        document.querySelector(".contact").classList.remove("contactAnim")
      }
      // Moreinfo
      if(document.querySelector("#moreinfo").getBoundingClientRect().top == 78){
        document.querySelector(".moreinfo").style.opacity = 1
        document.querySelector(".moreinfo").classList.add("moreinfoAnim")
      }
      else if(document.querySelector("#moreinfo").getBoundingClientRect().top > 660){
        document.querySelector(".moreinfo").style.opacity = 0
        document.querySelector(".moreinfo").classList.remove("moreinfoAnim")
      }
    })
  })

react-date-picker tile disabled not working

I have a project https://www.motoschool.co.nz/ with a booking section using react-date-picker. This was previously working with blocked off dates coming from datocms, but now even passing an inline tileDisabled function isn’t working e.g.

<DatePicker tileDisabled={({ activeStartDate, date, view }) => date.getDay() === 0} />

Has anyone else had a similar experience or know what could be going wrong/how to troubleshoot? I’ve already commented out and rebuilt everything based on the old website which is working and tried inline functions, clearing cache with gatsby clean, re-installing packages etc but I’m not sure why the tiles wouldn’t be disabled.

Any tips or suggestions for alternative calendar component would be appreciated!

Full page code below:

import React, {useRef, useEffect, useState} from "react"
import styled from "@emotion/styled"
import { useForm } from "react-hook-form"
import ReCAPTCHA from "react-google-recaptcha";
import { navigate } from "gatsby";
import { isWithinInterval } from "date-fns";
import DatePicker from 'react-date-picker'
import 'react-date-picker/dist/DatePicker.css';
import 'react-calendar/dist/Calendar.css';

const FormDiv = styled.div`
width: 694px;
// margin-right: 100px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
form {
    // padding: 50px;
    width: 694px;
    // margin-right: 20px;
    // max-width: 800px;
    min-height: 500px;
    height: 100%;
    max-height: 800px;
    // background-color: white;
    border-radius: 2px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    .select-style {
        // background-color: white;
        padding: 5px;
        border: solid 1px black;
        :hover {
            cursor: pointer;
        }
    }
    .button-style {
        margin-top: 40px;
        padding: 20px;
        background-color: white;
        color: black;
        border: none;
        border-radius: 10px;
        font-size: 14px;
        font-weight: 600;
        transition: .3s;
        :hover {
            cursor: pointer;
            // background-color: #635bff;
        }
    }
           .back {
    width: 28%;
    margin-right: 2%;
    }
    .next {
    width: 70%;
    }

    label {
        font-weight: 600;
        margin-top: 50px;
        // margin-left: 10px;
        margin-bottom: 4px;
    }
    input{
        font-size: 20px;
        padding: 15px;
        background-color: black;
        color: white;
        border-radius: 10px;
        border: 2px solid rgba(255,255,255,1);
        transition: .3s;
    }
    // input:hover {
    //     border: 1px solid rgba(0,0,0,0.5);
    // }
    input:focus-visible, textarea:focus-visible {
        border: 2px solid rgba(255,255,255,0.5);
        outline: 0;
    }
}
.time-selection {
        display: flex;
        flex-wrap: wrap;
        // justify-content: space-between;
        div {
            // border: solid 2px white;
            border: 2px solid rgba(255,255,255,0.2);
            border-radius: 10px;
            font-size: 16px;
            font-weight: 600;
            padding: 14px 14px;
            box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
            margin: 0 10px 10px 0;
            transition: .3s;
            // background-color: grey;
            :hover {
                cursor: pointer;
                border: 2px solid rgba(255,255,255,0.5);
            }
        }
        .active-time {
            background-color: white;
            color: black;
        }
}
.grecaptcha-badge { visibility: hidden!important; }
.recaptcha-sub {
font-size: 14px;
color: hsla(40,22%,92%,.6);
}
.react-date-picker__wrapper {
width: 100%;
padding: 15px;
  font-size: 20px;
  padding: 15px;
  background-color: black;
  color: white;
  border-radius: 10px;
//   border: 2px solid rgba(255,255,255,1);
  border: 2px solid rgba(255,255,255,0.2);
  -webkit-transition: .3s;
  transition: .3s;
  :hover {
  cursor: pointer;
  border: 2px solid rgba(255,255,255,0.5);
  }
  input {
  border: none;
  }
  input:focus-visible {
  border: none;
  }
}
.react-date-picker__calendar-button__icon, .react-date-picker__clear-button__icon {
stroke: white;
transition: .3s;
}
.react-date-picker__inputGroup__input {
padding: 0;
}
@media(max-width: 940px){
    width: 90vw!important;
    form {
    box-sizing: border-box;
    width: 90vw!important;
}

`

// function isWithinRange(date, range) {
//     console.log("isWithinRangeRunning", date, range)
//     return isWithinInterval(date, { start: range[0], end: range[1] });
// }
// function isWithinRanges(date, ranges) {
//     console.log("isWithinRangesRunning", date, ranges)
//     return ranges.some(range => isWithinRange(date, range));
// }
// let in3Days = new Date(2024, 11, 28);
// let in5Days = new Date(2024, 11, 28);
// let in13Days = new Date(2024, 11, 30);
// let in15Days = new Date(2024, 11, 31);

// let testDays = new Date(2024, 12, 26);

function isWithinRange(date, range) {
    console.log("isWithinRangeRunning")
    return isWithinInterval(date, { start: range[0], end: range[1] });
}
function isWithinRanges(date, ranges) {
    console.log("isWithinRangesRunning")
    return ranges.some(range => isWithinRange(date, range));
}
let in3Days = new Date(2025, 1, 28);
let in5Days = new Date(2025, 1, 28);
let in13Days = new Date(2025, 1, 26);
let in15Days = new Date(2025, 1, 26);

export default function ContactElectrical({datesUnavailable, setFormStage, timesAvailable, totalPrice, name, phone, email, adults, youth, lessonString, gearString, bikeString, hourString}){

    ///need to reformat dates here before adding to state, or do in useEffect

    const reRef = useRef();
    const [selectedDate, updateSelectedDate] = useState(new Date());
    const [serverState, setServerState] = useState({formSent: false});
    const [activeTime, setActiveTime] = useState(0)
    const [bookedDates, setBookedDates] = useState([ [in3Days, in5Days],[in13Days, in15Days],])
    // const [bookedDates, setBookedDates] = useState([ [in3Days, in5Days],[in13Days, in15Days],])

    console.log("datesUnavailable: ", datesUnavailable)
    // console.log("booked", bookedDates)

    useEffect(()=> {
        console.log("dates Unavailable running")
            let datesUnavailableRanges = []
            for(let i = 0; i < datesUnavailable.length; i++){
                let d = datesUnavailable[i].bookedDate.split("/")
                //except here they need to be in the right format so I need to seperate and make it so it's like this: new Date(2023, 11, 26);
                datesUnavailableRanges.push([new Date(Number(d[2]), Number(d[1])-1, Number(d[0])), new Date(Number(d[2]), Number(d[1])-1, Number(d[0]))])
            }
            setBookedDates(datesUnavailableRanges);
    },[datesUnavailable])

    // console.log("booked", bookedDates)
    console.log("tile", tileDisabled({date: in3Days, view: "month"}))

    // function tileDisabled({ date, view}) {
    //     console.log("test", view)
    //     console.log("date", date)
    //     console.log("test2", bookedDates)
    //     // Add class to tiles in month view only
    //     if (view === 'month') {
    //       // Check if a date React-Calendar wants to check is within any of the ranges
    //       console.log("test3", isWithinRanges(date, bookedDates))
    //       return isWithinRanges(date, bookedDates);
    //     }
    // }

    function tileDisabled({ date, view}) {
        // Add class to tiles in month view only
        if (view === 'month') {
          // Check if a date React-Calendar wants to check is within any of the ranges
          return isWithinRanges(date, bookedDates);
        }
      }


    const {
        register,
        handleSubmit,
        formState: { errors },
      } = useForm()


      async function onSubmit(data){
        // const reRef = useRef<>();
        const token = await reRef.current.executeAsync();
        reRef.current.reset();
        let dd = selectedDate.getDate();
        let mm = selectedDate.getMonth()+1;
        let yyyy = selectedDate.getFullYear();
        let reformattedDate = dd+"/"+mm+"/"+yyyy;
        fetch("/api/postmark-booking", {
          method: `POST`,
          body: JSON.stringify({
            name: name,
            email: email,
            phone: phone,
            adults: adults,
            youth: youth,
            lesson: lessonString,
            date: reformattedDate,
            time: timesAvailable[activeTime].time,
            gear: gearString,
            bike: bikeString,
            hours: hourString,
            totalPrice: totalPrice,
            token,
        }),
          headers: {
            "content-type": `application/json`,
          },
        })
          .then(res => res.json())
          .then(body => {
            console.log(`response from API:`, body);
          })
          .then(setServerState({formSent: true}))
      }
      console.log({ errors })
      useEffect(() => {
          if (serverState.formSent === true) {
            navigate("/booking-success/");
            setTimeout(() => {
                setServerState({
                    formSent: false
                })
            }, 3000)
          }
      })

  return (
            <FormDiv>
                <ReCAPTCHA 
                    sitekey={process.env.GATSBY_RECAPTCHA_SITE_KEY} 
                    size="invisible"
                    ref={reRef} 
                />
                 <form 
                 onSubmit={handleSubmit(onSubmit)} 
                 autocomplete="on">

          
                    <label htmlFor="email">BOOKING PERIOD:</label>
                    <DatePicker tileDisabled={tileDisabled} onChange={updateSelectedDate} value={selectedDate}  minDate={new Date(2025, 1, 9)} format="dd-MM-y"/>
                    {/* <DatePicker tileDisabled={({ activeStartDate, date, view }) => date.getDay() === 0} /> */}

                    <label>TIME SELECTION:</label>
                    <div className="time-selection">
                        {timesAvailable.map((time, i)=>(
                            <div key={"timeslot "+i} onClick={()=>setActiveTime(i)} className={i === activeTime ? "active-time" : ""}>{time.time}</div>
                        ))}
                    </div>

                    <h3>Total: ${totalPrice}</h3>
                    <p className="recaptcha-sub">This site is protected by reCAPTCHA and the Google <a href="https://policies.google.com/privacy">Privacy Policy</a> and <a href="https://policies.google.com/terms">Terms of Service</a> apply.</p>
                    
                    <div>
                    <button className="button-style back" onClick={(e) => {e.preventDefault();setFormStage(1)}}>BACK</button>
                    <button
                        onClick={() => setFormStage(3)}
                        type="submit" 
                        className="g-recaptcha button-style next"
                        data-sitekey="site_key"
                        data-callback='onSubmit'
                        data-action='submit'
                    >
                    SEND REQUEST</button>
                    </div>
                </form>
            </FormDiv>
  )
}

How do I write a promise chain with forks that depend on conditions?

I’m looking for a way to create a promise chain that has forks. By “forks” I mean the chain could go in two or more different directions depending on some condition. For example:

getIndexes(searchString).then(indexes => {
  if (indexes.length) return searchByIndex(indexes, 1, 5);
  else return searchAllRecords(searchString);
}.then(records => {
  // if searched all records
  return createIndexes(records, searchString);
  // else
  return records;
}).then( // how to handle multiple forks

As you can see, there are conditions in each then block and depending on the condition, it will return something different to the next then block by calling different functions. So we don’t know what each then block will receive as it depends on the outcomes of the previous conditions, and we have to do checks to see how it should be handled. This doesn’t seem ideal. What is the best way to build a promise chain when it contains forks in the flow?

How can I make an embedded HTML element behave as sticky between two specific sections in Wix velo?

How can I make an embedded HTML element behave as sticky between two specific sections in Wix?

Wix Studio Editor and Velo by Wix

I want an embedded HTML element to behave in the following way:

It should remain sticky between Section 1 and Section 2.
When scrolling to Section 2, the element should stick to a specific position (Point B).
As I scroll further to Section 3, it should remain at the same position in Section 2.
When scrolling back from Section 2 to Section 1, the element should move back to its original position (Point A).
In short, the HTML element should only act sticky between Section 1 and Section 2, and move as described when scrolling up or down.

I’ve tried several methods to achieve this, including:

Creating anchor points and manipulating the element using Velo by Wix.
Making the element sticky directly through Wix Studio.
Adjusting its position programmatically using the wix-window API.
However, none of these methods have worked to produce the desired behavior.

I was trying this method (the code below), but I am always having issues.
I tried sending the element position directly with postMessage, but I keep encountering problems.
Do you think I need to change the method, maybe by using Intersection Observer in Wix?

import wixWindowFrontend from 'wix-window-frontend';

$w.onReady(function () {
    trackScroll();
});

function trackScroll() {
    wixWindowFrontend.getBoundingRect()
        .then((windowSizeInfo) => {
            const scrollY = windowSizeInfo.scroll.y;
            const pointA = { top: 500, left: 100 }; 
            const pointB = { top: 800, left: 200 }; 
            const scrollRange = 300; 
            const htmlComponent = $w('#html1'); 

            let currentTop = 0;
            let currentLeft = 0;

            if (scrollY >= pointA.top && scrollY <= pointB.top) {
                const offset = (scrollY - pointA.top) / scrollRange;
                currentTop = pointA.top + (offset * (pointB.top - pointA.top));
                currentLeft = pointA.left + (offset * (pointB.left - pointA.left));

                htmlComponent.postMessage({
                    type: 'updatePosition',
                    style: {
                        top: `${currentTop}px`,
                        left: `${currentLeft}px`
                    },
                    additionalData: {
                        isVisible: true
                    }
                });
            } else if (scrollY > pointB.top) {
                currentTop = pointB.top;
                currentLeft = pointB.left;

                htmlComponent.postMessage({
                    type: 'updatePosition',
                    style: {
                        top: `${currentTop}px`,  
                        left: `${currentLeft}px`,
                    },
                    additionalData: {
                        isVisible: true
                    }
                });
            } else {
                htmlComponent.postMessage({
                    type: 'resetPosition',
                    style: {
                        top: '',
                        left: ''
                    },
                    additionalData: {
                        isVisible: false 
                    }
                });
            }

            setTimeout(trackScroll, 100);
        });
}

Additional information:
The element in question is an embedded HTML element. The scrolling behavior needs to be responsive and consistent across all devices. If anyone has a solution or best practices for achieving this, I’d appreciate your guidance!

How to draw HTML using vue 3 corresponding to every item in an array?

I’m trying to use Vue for the first time and I want to render HTML for each item in an array. Specifically, whenever a new item is added to the array, I would like the HTML to be updated accordingly.

Here’s what I’ve tried so far:

document.addEventListener('DOMContentLoaded', () => {

        const app = Vue.createApp({
            data() {
                return {
                    messages: []
                };
            },
            methods: {
                addMessage(message) {
                    this.messages.push(message);
                }
            },
            template: `
                <div class="list-group">
                    <div v-for="(message, index) in messages" :key="index" class="list-group-item">
                        <div class="d-flex">
                            <div class="p-2">
                                <i class="fa-solid fa-user fa-2xl text-primary"></i>
                                {{ message.role === 'user' ? '<i class="fa-solid fa-user fa-2xl text-primary"></i>' : '<i class="fa fa-robot fa-2xl text-primary"></i>' }}
                            </div>
                            <div class="p-2 flex-grow-1">{{ message.text }} <button type="button" class="copy-button">Copy</button></div>
                        </div>
                    </div>
                </div>  
            `
        });

        app.mount('#app');

        for (let i = 0; i < 10; i++) {

            app.addMessage({
                role: 'user',
                text: 'message #' + i
            });
        }
    });
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<div id="app"></div>

But keep getting an error in the console

Uncaught TypeError: app.addMessage is not a function

How can I resolve this issue?

Additionally, I want to add buttons to my template. After the HTML is rendered and added to the DOM, I would like to attach event listeners to these buttons. For example, for any button with the class copy-button, I want to add an event listener so that when the user clicks any button with that class, it logs “Hello” to the console.

How can I add event listeners to dynamically added elements after they are inserted into the DOM?

Overwrite a column of an array in AppScript

Say I have a 2D array with n rows, e.g.

table = [["Team 1", 3, 3, 0, 0, 9, 1],
         ["Team 2", 3, 0, 1, 2, 1, 3],
         ["Team 3", 3, 2, 0, 1, 6, 2],
         ["Team 4", 3, 0, 1, 2, 1, 3]]

Then say I have a 1D array of length n, e.g.

newRank = [1, 4, 2, 3]

How would I overwrite column i of table with newRank? E.g. column 6

Is there such thing as column slicing as in Python’s Numpy?

TypeError: u.then is not a function in Vue3+Vue router

Console Error

TypeError: u.then is not a function
    at index-CyodobdF.js:26:14273
    at Object.runWithContext (index-CyodobdF.js:14:10931)
    at we (index-CyodobdF.js:26:19934)
    at index-CyodobdF.js:26:23018

index-CyodobdF.js

function W(x, F, L) {
        Be(x);
        const k = ie.list();
        return k.length ? k.forEach(ee => ee(x, F, L)) : console.error(x), // here
        Promise.reject(x)
    }

It worked fine when running locally using ‘npm run dev’, but after deploying ‘npm run build’ using nginx, the following error occurs. After logging in as below link, an error occurs during the redirect operation using router(). I tried to solve it using the answers in the link, but in my case, it didn’t work much. Can you check if there is a problem with my router?

Also, I think there may be a problem with the code because we rushed to develop without knowing about Vue3.

/router/index.js


import { createRouter, createWebHistory } from 'vue-router'
import Marker from '@/assets/icon/filled/marker.svg'
import Book from '@/assets/icon/filled/book.svg'
import User from '@/assets/icon/filled/user.svg'
import CloseRec from '@/assets/icon/filled/close-rectangle.svg'
import Cookies from 'js-cookie'
import { logoutUser } from '@/lib/utils.js'

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
        {
            path: '/auth',
            component: () => import('../views/Layouts/AuthLayout.jsx'),
            redirect: '/auth/login',
            children: [
                {
                    path: 'login',
                    component: () => import('@/views/pages/LoginView.jsx'),
                    meta: {}
                },
                {
                    path: 'join',
                    component: () => import('@/views/pages/JoinView.jsx'),
                    meta: {}
                }
            ]
        },
        {
            path: '/diary',
            component: () => import('../views/Layouts/MainLayout.jsx'),
            redirect: '/diary/list',
            children: [
                {
                    path: 'list',
                    component: () => import('@/views/pages/MyDiaryView.jsx'),
                    meta: {
                        is_show: true,
                        title: '내 일기',
                        icon: Book, 
                        requiresAuth: true
                    }
                },
                {
                    path: 'write',
                    component: () => import('@/views/pages/WriteView.jsx'),
                    meta: {
                        is_show: true,
                        title: '일기 쓰기',
                        icon: Marker, 
                        requiresAuth: true
                    }
                },
                {
                    path: 'detail/:id',
                    component: () => import('@/views/pages/DetailView.jsx'),
                    meta: {
                        is_show: false,
                        title: '내 일기',
                        icon: Book, 
                        requiresAuth: true
                    }
                }
            ]
        },
        {
            path: '/account',
            component: () => import('../views/Layouts/MainLayout.jsx'),
            children: [
                {
                    path: 'info',
                    component: () => import('@/views/pages/AccountInfoView.jsx'),
                    meta: {
                        is_show: true,
                        title: '내 정보',
                        icon: User, 
                        requiresAuth: true
                    }
                },
                {
                    path: 'logout',
                    component: () => import('@/views/pages/LogoutView.jsx'),
                    beforeEnter: async (to, from, next) => {
                        try {
                            await logoutUser() 
                            next('/auth/login')
                        } catch (error) {
                            console.error('로그아웃 중 에러 발생:', error)
                            next('/auth/login') 
                        }
                    },
                    meta: {
                        is_show: true,
                        title: '로그아웃',
                        icon: CloseRec,
                        requiresAuth: true
                    }
                }
            ]
        }
    ]
})


router.beforeEach((to, from, next) => {
    const isLoggedIn = !!Cookies.get('is_login')

    if (to.path === '/') {
        next(isLoggedIn ? '/diary/list' : '/auth/login')
    } else if (to.meta.requiresAuth && !isLoggedIn) {
        Cookies.remove('is_login')
        next('/auth/login')
    } else {
        next()
    }
})

export default router

how to configure webpack to exclude certain modules from a library?

I have a React app that imports a library (in the node_modules directory, of course).

The name of the library is @lib/schema.

In the react app, modules of this library are imported like so:

import { module_name } from "@lib/schema"

There is a certain file in the @lib/schema that is currently being included in the final bundle, which is located in this path:

src/path/to/large_file.js

The file is never used by the app and thus unnecessarily increases the size of the bundle.

The React app currently uses react-scripts to create the bundle.

Questions:

  1. If the React app were to continue to use react-scripts, how to configure it to exclude src/path/to/large_file.js?
  2. If the React app were to use webpack, how to configure it to exclude src/path/to/large_file.js?