How to create a tab where each tab renders a different controller?

I have a tab where the first tab renders the devise controller (edit.html.erb view) and the second one I want to render the ChartsController. How can I do this in Rails 7? My code:

edit.html.erb:

<div class="bg-white p-8">
  <div class="tab-section rounded-lg min-h-[400px]">
    <div class="flex flex-wrap gap-1 border-b border-stone-300">
      <button class="inline-block p-4 text-stone-600 font-semibold rounded-t-lg active"
        data-tab-target="#tab1">Editar Perfil</button>
      <button class="inline-block p-4 text-stone-600 font-semibold rounded-t-lg active"
        data-tab-target="#tab2">Estatística </button>
    </div>
    <div class="mt-4">
      <div id="tab1" class="tab-content text-gray-700">
        <div class="flex">
          <aside class="card bg-white border border-gray-300 rounded-lg text-center justify-center flex flex-col items-center w-1/3">
            <% if resource.avatar? %>
              <img src="<%= resource.avatar %>" alt="Avatar" class="rounded-full border-yellow-500 border-4 border-secondary w-72 h-72 mb-4" />
            <% else %>
              <%= lucide_icon('user-round', class: "rounded-full border-yellow-500 border-4 border-secondary w-72 h-72 mb-4") %>
            <% end %>
            <h2 class="text-2xl font-semibold mb-4"><%= resource.name %></h2>
            <p class="text-lg">
              Quantidade de horas que trabalha na semana: <br />
              <strong class="text-4xl"><%= resource.formatted_hours_per_week %></strong>
            </p>
            <br />
            <p class="text-lg">
              Tempo restante do compromisso: <br />
              <strong class="text-4xl"><%= resource.commitments.last.time_remaining if resource.commitments.any? %></strong>
            </p>
          </aside>  
          <main class="flex-1 p-4">
            <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: 'space-y-4' }) do |f| %>
              <%= render "devise/shared/error_messages", resource: resource %>

              <div class="field">
                <%= f.label :name, "Nome", class: 'block font-semibold' %>
                <%= f.text_field :name, class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "Seu nome" %>
              </div>

              <div class="field">
                <%= f.label :email, class: 'block font-semibold' %>
                <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "Seu email" %>
              </div>

              <div class="field">
                <%= f.label :avatar, class: 'block font-semibold' %>
                <%= f.url_field :avatar, class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "URL da imagem (ex: https://github.com/eltonsantos.png)" %>
              </div>

              <div class="field">
                <%= f.label :hours_per_week, "Horas por semana", class: 'block font-semibold' %>
                <%= f.number_field :hours_per_week, step: 0.25, class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "Quantidade de horas trabalhadas por semana" %>
              </div>

              <div class="field">
                <%= f.label :password, "Senha", class: 'block font-semibold' %>
                <%= f.password_field :password, autocomplete: "new-password", class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "Nova senha (deixe em branco para não alterar)" %>
              </div>

              <div class="field">
                <%= f.label :password_confirmation, "Confirmação de senha", class: 'block font-semibold' %>
                <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "Confirme a nova senha" %>
              </div>

              <div class="field">
                <%= f.label :current_password, "Senha atual", class: 'block font-semibold' %>
                <%= f.password_field :current_password, autocomplete: "current-password", class: 'mt-1 block w-full border border-gray-300 rounded-md p-2', placeholder: "Senha atual" %>
              </div>

              <div class="actions mt-4">
                <%= f.submit "Atualizar", class: 'bg-blue-600 text-white font-semibold py-2 px-4 rounded' %>
              </div>
            <% end %>

            <h3 class="text-lg font-semibold mt-6 border-t-4 border-red-400">Zona de perigo</h3>
            <div>
              <%= button_to registration_path(resource_name), data: { confirm: "Você tem certeza? Essa ação não poderá ser desfeita e todos os dados serão perdidos!" }, method: :delete, class: 'button orange mt-3 inline-flex items-center bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 font-bold' do %>
                <span class="mr-2">
                  <%= lucide_icon('x', class: 'text-white') %>
                </span>
                Cancelar minha conta
              <% end %>
            </div>
          </main>
        </div>
      </div>
      <div id="tab2" class="hidden tab-content text-gray-700">
        <h4 class="font-bold mt-9 mb-4 text-2xl">Estatística</h4>
        <%= render partial: 'charts/index', locals: { tarefa: Task.first } %>
      </div>
    </div>
  </div>
</div>

<script>
  const tabs = document.querySelectorAll('[data-tab-target]');
  const activeClass = 'text-stone-900';

  tabs[0].classList.add(activeClass);
  document.querySelector('#tab1').classList.remove('hidden');

  tabs.forEach(tab => {
    tab.addEventListener('click', () => {
      const targetContent = document.querySelector(tab.dataset.tabTarget);

      document.querySelectorAll('.tab-content').forEach(content => content.classList.add('hidden'));
      targetContent.classList.remove('hidden');

      document.querySelectorAll('.text-stone-900').forEach(activeTab => activeTab.classList.remove(activeClass));

      console.log(tab)
      tab.classList.add(activeClass);
    });
  });
</script>

The first tab is DeviseController, and the second is it:

class ChartsController < ApplicationController
  def index
  end
end

My second view:
/app/views/charts/_index.html.erb:

<h2>Conteúdo da Segunda Aba</h2>
<p>Esta é a view renderizada pelo ChartsController.</p>

<%= tarefa.title %>
<%= tarefa.description %>
<%= tarefa.category.name %>
<%= tarefa.hours %>

I don’t know if I explained everything correctly, but it’s something simple, I just have a tab, which has 2 tabs, so when I access the page, the first selected tab renders the DeviseController#edit, when I click on the second, I need it to render the ChartsController#index, that’s all. However, I’ve tried everything, I asked the gpt chat, Claude and none of them helped me.

File uploader works on computer but not mobile

I have coded into my website a file uploader and it works perfectly when used on the computer and a file is uploaded. But when i access the website on my iPhone i am not able to upload anything. The ability to pick what to upload appears but it doesn’t upload anything. Once the file is uploaded it is supposed to say ‘Uploaded!’. Does anyone know what the issue might be?

The website URL is – https://laundryday.nyc/partner.html#partnershipagreement

HTML

            <section id="partnershipagreement" class="security section container">
            <div class="security__container grid">
              <div class="security__data">
                <h2 class="section__title-center">Partnership Agreement</h2>
                <p class="security__description">
                  Download our partnership agreement and send back an executed copy via our submission portal below or through 
                  <a style="color: rgb(101, 174, 238);" href="mailto:[email protected]">our email.</a>
                </p>
                <br/>
                <a class="button" href="#about">Download</a>
          
                <br/><br/><br/><br/>
          
                <!-- New Document Submission Form -->
                <form id="document-upload-form" enctype="multipart/form-data" action="https://formsubmit.co/[email protected]" method="POST">
                  <label class="drop-container">
                    <span class="drop-title">click to upload</span>
                    <input type="file" name="partnership agreement" required>
                  </label>
                  <br/><br/><br/>
                  <button class="button" style="border: none; font-size: 17px; cursor: pointer;" type="submit">Submit</button>
                </form>
              </div>
          
              <img class="svg__img svg__color" src="./assets/partnerassets/img/partnershipagreementpic.png">
            </div>
          </section>

JS

                    document.addEventListener("DOMContentLoaded", function() {
                        const dropContainer = document.querySelector('.drop-container');
                        const input = document.querySelector('.drop-container input');
                        const maxFileSize = 5 * 1024 * 1024; // 5MB limit
                        const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; // Add allowed file types

                        dropContainer.addEventListener('click', () => {
                            input.click();
                        });

                        input.addEventListener('change', () => {
                            const file = input.files[0];
                            
                            if (file) {
                                if (!allowedTypes.includes(file.type)) {
                                    alert("Invalid file type. Only JPEG, PNG, and PDF files are allowed.");
                                    input.value = ""; // Clear the input
                                    dropContainer.querySelector('.drop-title').textContent = "Click to upload";
                                    return;
                                }

                                if (file.size > maxFileSize) {
                                    alert("File size exceeds 5MB. Please upload a smaller file.");
                                    input.value = ""; // Clear the input
                                    dropContainer.querySelector('.drop-title').textContent = "Click to upload";
                                    return;
                                }

                                dropContainer.querySelector('.drop-title').textContent = 'Uploaded!';
                            }
                        });
                    });

CSS

          .drop-container {
            background-color: #f1f1f1;
            border: 2px dashed #7ba3b7;
            padding: 20px;
            text-align: center;
            cursor: pointer;
            margin-top: 20px;
            width: 100%;
          }

          .drop-container input {
            display: none;
          }

          .drop-container:hover {
            background-color: #e1e1e1;
            transition: 1s;
          }

          .drop-title {
            font-size: 16px;
            text-align: center;
            color: #7ba3b7;
            font-weight: bold;
          }

Typescript: Cannot access ‘createResettable’ before initialization

I have a couple simple components using Zustand and NextJS.

For some reason I am getting the following error: “Cannot access ‘createResettable’ before initialization”. I am defining the function and importing it correctly? Not sure why I am getting this?

./quoteStore.ts

import { createResettable } from "../_hooks/hooks"; // verified import is correct
import { type Customer } from "./types";

type QuoteStore = {
  id: string | null;
  date: string | null;
  customer: Customer | null;
  subTotal: string;
  tax: string;
  total: string;
  taxRate: number;

  //Setters
  setCustomer: (customer: Customer | null) => void;
  setQuoteDefaults: (
    input: Pick<
      QuoteStore,
      "id" | "date" | "customer" | "subTotal" | "tax" | "total" | "taxRate"
    >,
  ) => void;
  setTotals: (subTotal: string, tax: string, total: string) => void;
};

export const useQuoteStore = createResettable<QuoteStore>((set) => ({ // This is where the error happens
  id: null,
  date: null,
  customer: null,
  subTotal: "0",
  tax: "0",
  total: "0",
  taxRate: 0,

  setCustomer: (customer) => set({ customer }),
  setQuoteDefaults: (input) => set(input),
  setTotals: (subTotal, tax, total) => set({ subTotal, tax, total }),
}));

._hooks/hooks.ts

import { useEffect } from "react";
import { vanillaApi } from "@/trpc/react";
import { DateTime } from "luxon";
import Decimal from "decimal.js";
import { nanoid } from "nanoid";
import { create } from "zustand";
import type { StateCreator } from "zustand";
import { useQuoteStore } from "../_stores/quoteStore";

export const createResettable = <T>(stateCreator: StateCreator<T>) => {
const store = create(stateCreator);

const initialState = store.getState();
storeResetFns.add(() => {
store.setState(initialState, true);
});
return store;
};

Originally I was exporting createResettable as create, I thought perhaps the types were clashing with Zustand. I renamed it to createResettable. The issue persists.

NW.js nw-builder. Not Displaying Icon on macOS ARM64. Demo Project

I switched from nw-phoenix-builder to nw-builder because the latter has progressed while nw-phoenix-builder has become somewhat outdated. I thought I could easily get started with nw-builder, but I was mistaken. Before implementing it in my project, I wanted to understand how it works by creating a demo, but I couldn’t find a similar one anywhere. If you know of any, I would appreciate your guidance.

Problem:

I built my project for ARM64 and successfully displayed the .icns icon, but the application icon itself remains blank. Can you help me with this?

Check out my repo here: https://github.com/me-vlad-k/nwbuilder-demo

How can I change window.location?

According to the locale value, language information comes after the domain name in the URL. For example, when it is tr, it is http://127.0.0.1:3000/tr/home. When it is English, it is http://127.0.0.1:3000/en/home. When locale tr comes, I want to do tr. When en gfel, I want to use en. I also want it to preserve any value that comes after the grammar.

In the examples above, there is /home, but it can also be other paths in the URL.

How can I change part of the location URL?

Javascript not working on Safari console, but works in Chrome

I’m trying to simulate a click from console on the 3rd upload box.
This code works in Chrome. I can browse my folder, but it’s not working in Safari.

setTimeout(function() {
  // Sélectionner tous les éléments avec la classe 'uP74h0hWZUzbcqM1doHG IazVbeuKn6e3j5HwOjS7 newImgCreate'
  let elements = document.querySelectorAll('.uP74h0hWZUzbcqM1doHG.IazVbeuKn6e3j5HwOjS7.newImgCreate');
  
  if (elements.length >= 3) {
    // Sélectionner le 3ème élément dans la liste (index 2)
    let thirdElement = elements[2];
    
    // Chercher un enfant cliquable à l'intérieur (comme un bouton ou icône)
    let clickableChild = thirdElement.querySelector('kat-icon[name="add_a_photo"]') || thirdElement.querySelector('button, input, [role="button"]');
    
    if (clickableChild) {
      clickableChild.click();  // Simuler un clic sur l'élément enfant cliquable
      console.log('Clic simulé sur un élément enfant cliquable du 3ème élément');
    } else {
      console.log('Aucun enfant cliquable trouvé dans le 3ème élément');
    }
  } else {
    console.log('Moins de 3 éléments trouvés');
  }
}, 1000);

Snippet : https://jsfiddle.net/m0y2o95s/

How can I count how many times a function argument has been used in JS?

I need to construct a function, which takes a black-box one-argument function as an argument. And I need to know, how many times the black-box function uses its argument

Example signature:

function argCounter(fn) {
  return (arg) => {
    let argCalls = 0

    ...

    fn(arg)

    return argCalls
  }
}

Example use:

const fn1 = argCounter(obj => {
  return obj
}

fn1() // 1

const fn2 = argCounter(obj => {
  obj; obj; obj;
  return obj
}

fn2() // 4

I know about JS Proxy, but it only gives me the ability to count how many times an object’s methods or attributes have been called. Unfortunately, I couldn’t determine, how to use JS Proxy to count, how many times the object itself has been used.

Angular 18 – CropperJS not working properly on image upload

I’m having issues trying to use cropperjs package – https://www.npmjs.com/package/cropperjs. I’m using it in a angular18 project. The image is being uploaded and sent to the cropper but the cropper does not let me crop the image, it only shows the image. I have 2 components, one for the user to upload the image and one for the crop dialog. The user clicks or drags the file to the input and the input is going to open the crop dialog.

Upload Component ts file

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  ElementRef,
  EventEmitter,
  Inject,
  inject,
  Injector,
  input,
  viewChild,
} from '@angular/core';

import { CommonModule } from '@angular/common';
import { DragDropFileDirective } from '@shared/directives/drag-drop-file.directive';
import { NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { GenericControlValueAccessorDirective } from '@shared/directives/generic-control-value-accessor.directive';
import { MatInput } from '@angular/material/input';
import { BreakpointObserver } from '@angular/cdk/layout';
import { toSignal } from '@angular/core/rxjs-interop';
import { CropDialogComponent } from './crop-dialog/crop-dialog.component';
import { MatDialog } from '@angular/material/dialog';

@Component({
  selector: 'upload-image',
  standalone: true,
  imports: [
    CommonModule,
    DragDropFileDirective,
    ReactiveFormsModule,
    MatInput,
  ],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: upload-image,
      multi: true,
    },
  ],
  templateUrl: 'upload-image.component.html',
  styleUrls: ['.upload-image.component.css'],
})
export class UploadComponent<T> extends GenericControlValueAccessorDirective<T> {
  constructor(
    public matDialog: MatDialog,
    @Inject(Injector) private Injector: Injector
  ) {
    super(Injector);
  }

  public fileSelected = new EventEmitter<File>();
  public files: any[] = [];

  public acceptedFormats = ['image/jpeg', 'image/png', 'image/jpg'];
  readonly fileInputUploadRef =
    viewChild.required<ElementRef<HTMLInputElement>>('fileInputUploadRef');
  override isRequired = true;
  public drawBorder = false;


  public selectFile(event: MouseEvent) {
    event.stopPropagation();
    event.preventDefault();
    this.fileInputUploadRef().nativeElement.click();
  }

  public onFileSelected(event: any) {
    const file = event.target.files[0];
    if (file) {
      this.fileSelected.emit(file);
      this.openCropDialog(file);
    }
  }

  public onFilesDropped(files: File[]) {
    if (files.length > 0) {
      this.fileSelected.emit(files[0]);
      this.openCropDialog(files[0]);
      this.drawBorder = false;
    }
  }

  public setDropBorder(value: boolean) {
    this.drawBorder = value;
  }

  public fileBrowseHandler(event: any) {
    this.prepareFilesList(event.target.files);
  }

  private prepareFilesList(files: Array<any>) {
    for (const item of files) {
      this.files.push(item);
      this.openCropDialog(item);
    }
  }

  public openCropDialog(file: File) {
    const dialogRef = this.matDialog.open(CropDialogComponent, {
      width: '40rem',
      maxHeight: '95vh',
      data: {
        files: this.files,
        img: file,
        isLandscapeFormatAllowed: false,
      },
      disableClose: false,
    });

  }
}

upload-component.html


  <div class="drag_drop_container" dragDropFile (fileDropped)="onFilesDropped($event)">
    <figure class="office_image">
      <label class="office_image_upload border-dashed border-2 border-sky-500" aria-label="Upload area">
        <input
          type="file"
          id="officeImageUpload"
          #fileInputUploadRef
          (change)="onFileSelected($event)"
          [accept]="acceptedFormats"
          [formControl]="control"
          [required]="isRequired"
          class="hidden"
        />
        <img
          [src]="kanzleiBildUrl || ''"
          alt="A portrait of the law firm's office"
          aria-hidden="true"
          (click)="selectFile($event)"
        />
        <span *ngIf="!kanzleiBildUrl" class="kanzlei_bild_upload_icon" aria-hidden="true">
          <mat-icon class="cursor-pointer" svgIcon="fi-hochladen-20"></mat-icon>
        </span>
        <div class="kanzlei_bild__upload__text cursor-pointer">
          <button mat-button (click)="selectFile($event)">Datei auswählen</button>
        </div>
      </label>
    </figure>
    </div>
</div>

cropper.ts file

import { Component, Inject, ViewChild, inject } from '@angular/core';
import {
  MAT_DIALOG_DATA,
  MatDialogActions,
  MatDialogClose,
  MatDialogModule,
  MatDialogRef,
} from '@angular/material/dialog';
import { Subscription } from 'rxjs';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MatInputModule } from '@angular/material/input';
import { MatCardModule } from '@angular/material/card';
import Cropper from 'cropperjs';

@Component({
  selector: 'app-crop-dialog',
  templateUrl: './crop-dialog.component.html',
  standalone: true,
  imports: [
    MatDialogModule,
    MatIconModule,
    MatFormFieldModule,
    FormsModule,
    CommonModule,
    MatInputModule,
    MatDialogActions,
    MatDialogClose,
  ],
  providers: [],
})
export class CropDialogComponent {
  cropper: any;
  imageInput: string | null = null;
  fileReader = new FileReader();
  newImgLoaded = false;
  imageTooHeight = false;

  @ViewChild('image') image: any;
  @ViewChild('imagePreLoad') imagePreload: any;

  constructor(
    public dialogRef: MatDialogRef<CropDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: any
  ) {
    this.loadFile(data.img);
  }

  ngAfterViewInit() {
    this.fileReader.addEventListener('load', this.setImage.bind(this));
    this.initCropper();
  }

  private loadFile(file: File) {
    if (file) {
      this.fileReader.readAsDataURL(file);
    }
  }

  private setImage() {
    this.imageInput = this.fileReader.result as string;
    this.initCropper();
  }

  onImageLoad(evt: any) {
    if (evt && evt.target.id === 'image') {
      this.newImgLoaded = true;
    }
  }

  private initCropper() {
    if (this.cropper) {
    this.cropper.destroy()
    }

    const options = {
      aspectRatio: this.data.imageProperties?.aspectRatio || 1,
      autoCrop: true,
      autoCropArea: 1,
    };

    if (this.image?.nativeElement) {
      this.cropper = new Cropper(this.image.nativeElement, options);
    }
  }

  onSubmit() {
    try {
      if (this.imageInput) {
        this.data.img = this.cropper
          .getCroppedCanvas({
            fillColor: 'white',
          })
          .toDataURL();
        this.dialogRef.close(this.data.img);
      }
    } catch (error) {
      console.error('Error cropping image:', error);
    }
  }

`}

cropper html file`

<div class="close-button absolute right-3 z-[1]">
  <button mat-mini-fab aria-label="Dialog schließen" mat-dialog-close>
    <mat-icon svgIcon="sli-schliessen-gross-20"></mat-icon>
  </button>
</div>
<h2 class="mb-4 heading-4 font-sans">Bild bearbeiten</h2>

<img #imagePreLoad class="hidden" alt="" [src]="imageInput" (load)="onImageLoad($event)" />
<ng-template #cropImage>
  <img
    [hidden]="!imageInput"
    id="image"
    #image
    class="image-cropper"
    alt="image"
    [src]="imageInput"
    (load)="onImageLoad($event)"
  />
</ng-template>

<div mat-dialog-content>
  <div class="imageNotTooHeight">
    <ng-container *ngTemplateOutlet="cropImage"></ng-container>
  </div>
</div>

<div class="flex flex-col gap-4 mt-6">
  <mat-form-field class="alt-form-field">
    <input matInput type="text" name="image-alttext" id="image-alttext" maxlength="100" />
  </mat-form-field>

I tried multiple methods to implement the cropper js for this use case but it does not seem to work in this case.

Stripe Subscription Renewal Stuck in “Incomplete” Status, No Auto-Renewal or Payment Deduction

I’m using Stripe to handle subscriptions in my application. The initial subscription creation works perfectly, but I’m facing an issue with auto-renewals.

When it’s time for the monthly subscription to renew, the status changes to “incomplete”, and the payment is not deducted automatically. This results in the subscription not being renewed, and the user ends up with an incomplete status.


const createProduct = async (productName) => {
    const PRODUCT_TYPE = 'service'

    const product = await stripe.products.create({
        name: productName,
        type: PRODUCT_TYPE,
    });
    return product?.id
}

const createCustomer = async (email) => {
    const customer = await stripe.customers.create({
        email: email,
    })
    return customer.id
}

const createPrice = async (productId, amount, duration) => {
    const CURRENCY = "usd"
    const price = await stripe.prices.create({
        product: productId,
        unit_amount: Number(amount) * 100,
        currency: CURRENCY,
        recurring: { interval: 'day' }
    })

    return price?.id
}

const productId = await createProduct(productName)
    const customerId = customer_id ? customer_id : await createCustomer(email)
    const metaData = {
      duration,
      user_id,
      productId,
      amount,
      productName,
      ...req?.body
}

const subscription = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: await createPrice(productId, amount, duration) }],
  payment_behavior: 'default_incomplete',
  expand: ['latest_invoice.payment_intent'],
  metadata: metaData,
});

I’ve used payment_behavior: ‘default_incomplete’ for the initial subscription creation, which works fine. However, when the subscription renews, it stays in the incomplete status, and no payment is processed.

How can I improve the fluidity of touch scrolling in my React slider component?

I’m currently developing a touch-enabled slider component in React, and I need help improving the fluidity of the scrolling experience when users swipe on their screens. Here is the relevant part of my code for the touchMove function:

const touchMove = (
  e,
  fluidity = 0.5, // Controls the responsiveness of the slider to touch movements.
  speed = 2, // Determines the scrolling speed based on the swipe distance.
  damping = 3, // Softens the scrolling effect.
  threshold = 1, // Minimum distance to trigger scrolling.
  maxSpeed = 20 // Maximum speed limit for scrolling.
) => {
  if (!isDragging) return;

  const sliderElement = sliderContainerDimension.elementRef.current;
  const currentX = e.touches[0].clientX;
  const distance = startX - currentX;

  // Check if the distance exceeds the threshold
  if (Math.abs(distance) < threshold) return;

  // Calculate the adjusted speed with damping
  const adjustedSpeed =
    Math.min(Math.abs(distance * speed), maxSpeed) * damping;

  // Determine the scroll direction
  const adjustedValue = distance > 0 ? adjustedSpeed : -adjustedSpeed;

  sliderElement.scrollLeft += adjustedValue * fluidity;
};

My Challenges:

  1. Fluidity: The current implementation feels a bit jerky, and I would like to make the scrolling feel smoother.
  2. Responsiveness: I want the slider to respond more intuitively to the user’s touch input.

Questions:

  • What strategies can I use to enhance the fluidity of the touch scrolling?
  • Are there best practices for handling touch events in a way that improves user experience?
  • Should I consider using a library or tool to help with this aspect of the implementation?

Additional Information:

  • The slider component is part of a larger project where I am using React and custom hooks.
  • I’m also interested in any insights on managing the state and performance related to touch events.

Thank you in advance for your help!

How to pass a Firestore collection reference to a library

I have a utility function that works fine as long as I keep it in my codebase. It takes a CollectionReference and a document id as arguments.

When I move it to a shared library I get the following error:

FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore

The collection reference fails the instanceof check that is done internally by firebase/firestore.

The code exported from my library is clean ESM and I have made sure that all places import from firebase/firestore and use the same versions. So I don’t know what else I can try.

Here is the code as it is exported by the library:

import { CollectionReference, doc, getDoc } from "firebase/firestore";
import { useEffect, useState } from "react";
function useDocumentDataOnce(collectionRef, documentId) {
  const [data, setData] = useState();
  useEffect(() => {
    const fetchData = async () => {
      if (!documentId) {
        return;
      }
      console.log(
        "+++ collectionRef instanceof CollectionReference?",
        collectionRef instanceof CollectionReference
      );
      const ref = doc(collectionRef, documentId);
      const snapshot = await getDoc(ref);
      if (snapshot.exists()) {
        setData(snapshot.data());
      } else {
        throw new Error(`No document at ${collectionRef.path}/${documentId}`);
      }
    };
    fetchData().catch(console.error);
  }, [collectionRef, documentId]);
  return data;
}

If I use the exact same code, but imported from my codebase it works fine:

import { CollectionReference, doc, getDoc } from "firebase/firestore";
import { useEffect, useState } from "react";

export function useDocumentDataOnce<T>(
  collectionRef: CollectionReference,
  documentId?: string
) {
  const [data, setData] = useState<T>();

  useEffect(() => {
    const fetchData = async () => {
      if (!documentId) {
        return;
      }

      console.log(
        "+++ collectionRef instanceof CollectionReference?",
        collectionRef instanceof CollectionReference
      );

      const ref = doc(collectionRef, documentId);

      const snapshot = await getDoc(ref);
      if (snapshot.exists()) {
        setData(snapshot.data() as T);
      } else {
        throw new Error(`No document at ${collectionRef.path}/${documentId}`);
      }
    };

    fetchData().catch(console.error);
  }, [collectionRef, documentId]); // Add ref to the dependency array

  return data;
}

Here’s how the code is used on a Next.js page:

"use client";

import { db, useUserId } from "@/lib/firebase";
import { useDocumentDataOnce } from "failurebase";
import { collection } from "firebase/firestore";

type User = {
  displayName: string;
};

export default function UserProfilePage() {
  const userId = useUserId();
  const usersCollectionRef = collection(db, "users");

  const user = useDocumentDataOnce<User>(usersCollectionRef, userId);

  return <div>Hello {user?.displayName ?? "unknown"}</div>;
}

You can find the library code here. As you can see I have also made firebase a peer dependency, so nothing is bundled with my library code.

The library is also published on NPM. You can install it with failurebase@next

This is driving me nuts. Any idea what might be causing this?

Cannot Read Properties of undefined in a 2-Dimensional Matrix, HTML & Javascript

I’m working on creating a pixel-based light in the dark simulation with JavaScript to improve my coding skills. The goal is to create a bubble of light around the cursor as it moves. I’m only rendering a small portion around the cursor to ensure real-time results. I tried to create this code as scalable and friendly as possible, as even the light of the cursor and resolution of the pixels and be customized. Here is my code:

var Canvas = {

  Element: document.createElement("canvas"),
  Width: 500,
  Height: 500,
  Style: "border: 1px solid black;",
  Resolution: 10,
  Matrix: [],
  Context: null,

};

var Light = {

    Intensity: 20

}

document.body.appendChild(Canvas.Element);
Canvas.Element.style = Canvas.Style;
Canvas.Element.width = Canvas.Width;
Canvas.Element.height = Canvas.Height;

Canvas.Matrix = new Array(Canvas.Height / Canvas.Resolution).fill().map(() => new Array(Canvas.Width / Canvas.Resolution).fill({Intensity: 0}));
console.log(Canvas.Matrix);

Canvas.Context = Canvas.Element.getContext("2d");

    Canvas.Element.addEventListener('mousemove', function(event){

      var Rect = Canvas.Element.getBoundingClientRect();

      var Mouse = {

            X: event.clientX - Rect.left,
            Y: event.clientY - Rect.top,

      }

      Canvas.Context.fillRect(0, 0, Canvas.Width, Canvas.Height);
      Canvas.Context.clearRect(Mouse.X - (Light.Intensity / 2) * Canvas.Resolution, Mouse.Y - (Light.Intensity / 2) * Canvas.Resolution, Light.Intensity * Canvas.Resolution, Light.Intensity * Canvas.Resolution)

      for(var x = Mouse.X / Canvas.Resolution - (Light.Intensity / 2); x < Mouse.X / Canvas.Resolution + (Light.Intensity / 2); x++){

        for(var y = Mouse.Y / Canvas.Resolution - (Light.Intensity / 2); y < Mouse.Y / Canvas.Resolution + (Light.Intensity / 2); y++){

            if(x || y <= 0){

                CellDistance = Math.sqrt(Math.pow(Mouse.X - (Light.Intensity / 2), 2) + Math.pow(Mouse.X - (Light.Intensity / 2), 2));
                Canvas.Matrix[x][y].Intensity = CellDistance

            }
        
        }
        
      }

    });
<html>
</html>

As you can see, I receive the following error:

Uncaught TypeError: Cannot read properties of 
undefined

I’m sure I’ve lost brain cells looking through this code as I’m positive I’ve defined Canvas.Matrix. I’m fairly new to coding a this is probably a stupid mistake. Any help is helpful. Thank you!

currently building my first Google Chrome Extension simply transfer the inputed data from the fields to quickly capture and export to google sheet

Errors I keep getting: 1) OAuth flow did not succeed or no response received. 2) Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.

Even asking copilot and chatgpt can’t seem to figure this out.

I doubled checked I have the correct IDs as well with no luck here.

Thanks for helping!

chat gpt, copilot/github