Automatically change user role after completed orders in WooCommerce

I’m using code that gives the user a new role after three orders.

The default user role is “Customer” and I have created an additional role “Regular Customer”.

/* Create a new role */
add_action('init', 'luckydoll_add_custom_user_role');
function luckydoll_add_custom_user_role() {
    add_role('regular_customer', __('Regular Customer', 'woocommerce'), array(
        'read' => true,
        'create_posts' => false,
    ));
}

/* Change user role after three orders */
function total_completed_orders( $the_user_id ) {
    global $wpdb;
    
    $count = $wpdb->get_var(
        // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
        "SELECT COUNT(*)
        FROM $wpdb->posts as posts
        LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
        WHERE   meta.meta_key = '_customer_user'
        AND     posts.post_type = 'shop_order'
        AND     posts.post_status = 'wc-completed'
        AND     meta_value = '" . esc_sql( $the_user_id ) . "'"
        // phpcs:enable
    );

    return $count;
}

add_action( 'woocommerce_order_status_completed', 'action_woocommerce_order_status_completed', 10, 1 );
function action_woocommerce_order_status_completed( $order_id ) {
    
    // User role in which the customer role must be changed
    $premium_user_role = 'regular_customer';
    
    // Spending min cash in one order
    $min_in_one_order = 400;
    
    // Change role after x completed orders
    $change_role_after_x_completed_orders = 3;
    
    $flag = false;
    
    // Get an instance of the WC_Order object
    $order = wc_get_order( $order_id );
    
    // Is a order
    if( is_a( $order, 'WC_Order' ) ) {
        // Get user ID or $order->get_customer_id();
        $user_id = $order->get_user_id();
        
        // Retrieve user meta field for a user. 
        $is_premium = get_user_meta( $user_id, '_customer_is_premium_member', true );
        
        // Is NOT a premium member
        if ( $is_premium != 1 ) {
            // Count total completed orders
            $total_completed_orders = total_completed_orders( $user_id );
            
            // Total completed orders greater than or equal to change role after x completed orders
            if ( $total_completed_orders >= $change_role_after_x_completed_orders ) {
                $flag = true;
            } else {
                // Get current order total
                $order_total = $order->get_total();
                
                // Order total greater than or equal to minimum in one order
                if ( $order_total >= $min_in_one_order ) {
                    $flag = true;
                }
            }
            
            // True, at least 1 condition has been met
            if ( $flag ) {
                // Get the user object
                $user = get_userdata( $user_id );

                // Get all the user roles as an array.
                $user_roles = $user->roles;
                
                // User role contains 'customer'
                if ( in_array( 'customer', $user_roles ) ) {
                    // Remove customer role
                    $user->remove_role( 'customer' );
                    
                    // Add premium role
                    $user->add_role( $premium_user_role );
                    
                    // Update user meta field based on user ID, set true
                    update_user_meta( $user_id, '_customer_is_premium_member', 1 );
                }
            }
        }
    }
}

When a registered user places three orders, their role automatically changes.

But if a guest makes an order and the manager sets his order to the “Done” status, a critical error appears. enter image description here

line 347: $user_roles = $user->roles;
line 350: if ( in_array( 'customer', $user_roles ) ) {

I can disable guest checkout. On the other hand, I can’t force guests to register on the site if they don’t want to.

How can this error be fixed? Is it possible to exclude guests from this custom code?

I get this error when I activate my WooCommerce Plugin:

thrown in /home/tenthspor/public_html/wp-content/plugins/woocommerce/packages/action-scheduler/classes/data-stores/ActionScheduler_DBLogger.php on line 29

PHP Warning: include(/home/tenthspor/public_html/wp-content/plugins/woocommerce/includes/admin/views/html-notice-update.php): Failed to open stream: No such file or directory in /home/tenthspor/public_html/wp-content/plugins/woocommerce/includes/admin/class-wc-admin-notices.php on line 400

PHP Warning: include(/home/tenthspor/public_html/wp-content/plugins/woocommerce/includes/admin/views/html-notice-update.php): Failed to open stream: No such file or directory in /home/tenthspor/public_html/wp-content/plugins/woocommerce/includes/admin/class-wc-admin-notices.php on line 400

PHP Warning: include(): Failed opening ‘/home/tenthspor/public_html/wp-content/plugins/woocommerce/includes/admin/views/html-notice-update.php’ for inclusion (include_path=’.:/opt/alt/php81/usr/share/pear:/opt/alt/php81/usr/share/php:/usr/share/pear:/usr/share/php’) in /home/tenthspor/public_html/wp-content/plugins/woocommerce/includes/admin/class-wc-admin-notices.php on line 400

PHP Warning: require(/home/tenthspor/public_html/wp-content/plugins/woocommerce/src/Internal/WCCom/ConnectionHelper.php): Failed to open stream: No such file or directory in /home/tenthspor/public_html/wp-content/plugins/woocommerce/vendor/jetpack-autoloader/class-php-autoloader.php on line 102

PHP Warning: require(/home/tenthspor/public_html/wp-content/plugins/woocommerce/src/Internal/WCCom/ConnectionHelper.php): Failed to open stream: No such file or directory in /home/tenthspor/public_html/wp-content/plugins/woocommerce/vendor/jetpack-autoloader/class-php-autoloader.php on line 102

PHP Fatal error: Uncaught Error: Failed opening required ‘/home/tenthspor/public_html/wp-content/plugins/woocommerce/src/Internal/WCCom/ConnectionHelper.php’ (include_path=’.:/opt/alt/php81/usr/share/pear:/opt/alt/php81/usr/share/php:/usr/share/pear:/usr/share/php’) in /home/tenthspor/public_html/wp-content/plugins/woocommerce/vendor/jetpack-autoloader/class-php-autoloader.php:102
Stack trace:

Threejs shadow not rendered properly

shadow not rendered as expected

Why shadows are not rendered properly and there are gradient like issues

const light = new DirectionalLight(tint, 2.5);
light.position.set(-20, 90, 4);
const helper = new CameraHelper(light.shadow.camera);
this.scene.add(helper);
light.castShadow = true;
const factor = 2;
light.shadow.mapSize.width = 512 * factor;
light.shadow.mapSize.height = 512 * factor;
const val = 1000;
light.shadow.camera.left = -val;
light.shadow.camera.right = val;
light.shadow.camera.top = val;
light.shadow.camera.bottom = -val;
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 100;
this.scene.add(light);

foundation slider with active thumbnail in the visible area

i’m using the orbit modul from foundation that works fine. I have reused the area of the bullets for the thumbnails of the large slider images. This also works so far, the active thumbnail is also marked as active.

Foundation has created an area here that expands downwards. I have arranged the thumbnails in a row using CSS.

My problem here is that after a few thumbnails the active one disappears from the visible area. I would like to have the active thumbnail in the visible area. Is there a solution from Foundation that I have not discovered? How would I have to add the JavaScirpt for this? Can anyone give me a tip here?

I would like to switch to another slider, because the Foundation Slider offers the possibility to display the images in the slider for the respective size of the browser viewport.

If you know of another slider that has a similar function, I would also be interested.

Many thanks for your help
m.orange

How to create a layout that only has all even or all odd row size, and changes by only 2?

I have been iterating with both Claude 3.7 Sonnet, and ChatGPT 4o, for days and days, trying to get this to be exactly how I want it, but it keeps making mistakes in the algorithm, and on an on in making the same mistakes in basically a circle.

How do I make a “layout” (array of integers), which represents a grid, which has the following constraints:

  1. The function is generateGridLayout(n, minColumns, maxColumns), where practically speaking it is usually called as gen(n, 3, 7), but could in theory be anything which would make a nice grid, say gen(n, 2, 256) as the max range.
  2. It should handle arbitrary number of elements (say up to Number.MAX_SAFE_INTEGER, which is 9007199254740991, but practically my “grid layouts” will only have mainly up to 1000 items).
  3. If the number of items n is odd, then each row should only have an odd number of values. If n is even, then each row can have either even or odd numbers (think 30, can be 10 rows of 3, or 3 rows of 10).
  4. The rows can only ever differ by 2 in size, always decreasing. It can’t ever differ by 3, 4, etc.. This means that 7 CANNOT be [5, 2] or [4, 4, 1], as those have jumps > 2. It can only be [7] or [3, 3, 1], if gen(7, 3, 7).
  5. (meta note, this is for a UI layout, so it is based on viewport/container size, so if we say “max is 7”, but there is only space for 5, it will set the max to 5, this fact isn’t really relevant for the solution though)
  6. If we say “max is 7”, but there is an even number of items, and the even number can’t satisfy “all even or all odd rows”, then try maxColumns - 1, and so on, down to minColumns.
  7. Important: it should minimize the number of small sized rows. So for 29, at 6 maxColumns, it should be [5, 5, 5, 5, 5, 3, 1], not [5, 5, 5, 5, 3, 3, 3]. That is, it maximizes the number of large rows, and minimizes the numbers of small rows. Likewise for 29, it should definitely not be [5, 5, 5, 5, 3, 3, 1, 1, 1].

Here are some examples to demonstrate the goal:

31
[5, 5, 5, 5, 5, 3, 3]
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1]

30
[5, 5, 5, 5, 5, 5]
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]

29
[5, 5, 5, 5, 5, 3, 1]
[3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1]

28
[6, 6, 6, 6, 4]
[4, 4, 4, 4, 4, 4, 4]

27
[5, 5, 5, 5, 3, 3, 1]
[3, 3, 3, 3, 3, 3, 3, 3, 3]

26
[6, 6, 6, 4, 2, 2]
[4, 4, 4, 4, 4, 4, 4]

23
[5, 5, 5, 5, 3]
[3, 3, 3, 3, 3, 3, 3, 1, 1]

These show, if 5 or 6 is the max columns, what it would do, and what it should do if 4 or 3 is the max columns.

For example, 26 at 7 max columns should NOT be:

[6, 6, 6, 6, 2] # jumps more than 2
[4, 4, 4, 4, 4, 4, 2] # doesn't maximize maxColumns

It should ideally be:

[6, 6, 6, 4, 2, 2] # jumps max 2, maximizes large columns, minimizes small columns.

Here is my current solution:

log(29, 2, 7)
log(29, 2, 6)
log(29, 2, 5)
log(29, 2, 4)
log(44, 2, 3)

function distributeGridLayout(
  length,
  minColumns,
  maxColumns
) {
  function recur(
    dp,
    length,
    width,
  ) {
    if (length == 0) {
      return []
    }

    if (length < width - 2 || width <= 0) {
      return
    }

    if (dp[width].has(length)) {
      return
    }

    dp[width].add(length)

    for (let i = 0; i < 2; i++) {
      let result = recur(dp, length - width, width)
      if (result) {
        return [width, ...result]
      }
      width -= 2
    }

    return
  }

  if (length <= maxColumns) {
    return [length]
  }

  if (maxColumns >= 3 && length === 7) {
    return [3, 3, 1]
  }

  if (maxColumns >= minColumns && length % maxColumns === 0) {
    const result = []
    while (length) {
      result.push(maxColumns)
      length -= maxColumns
    }
    return result
  }

  if (maxColumns > 4) {
    if (maxColumns > minColumns && length % (maxColumns - 1) === 0) {
      const result = []
      maxColumns--
      while (length) {
        result.push(maxColumns)
        length -= maxColumns
      }
      return result
    }
  }

  const dec = 2 - (length % 2)

  maxColumns -= maxColumns % dec

  const dp = Array.from(
    { length: maxColumns + 1 },
    () => new Set(),
  )

  for (let width = maxColumns; width > 0; width -= dec) {
    const result = recur(dp, length - width, width)
    if (result) {
      if (width <= minColumns) {
        return
      }
      return [width, ...result]
    }
  }

  return
}

function log(n, min, max) {
  const grid = distributeGridLayout(n, min, max)
  console.log(`gen(${n}, ${min}, ${max})`, grid)
}

That is working for most, like this Tibetan layout (29 chars):

But it is not working for this Thai layout (44 chars, in 2-3 columns), here is the end of it (in my UI, if the algorithm returns undefined, it falls back to a basic grid layout):

What do I need to change exactly so this always fits my rules? The 44 layout of 3 max 2 min should be basically a 2 column layout…

ChartJS make container fill whole CSS grid item

I’m putting a chart (made using Chart.js) inside a CSS grid, and I want the chart to responsively fill the full width of its grid cell. However, even after turning on responsive and turning off maintainAspectRatio for the chart, the chart does not redraw to fill the container if the viewport is widened enough.

To reproduce:

<main>
    <div id="chart-container">
        <canvas id="chart"></canvas>
    </div>
    <div id="other">Some other data here</div>
</main>

<style>
    main {
        width: 100%;
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-template-areas:
            'chart chart more';
    }
    #chart-container {
        grid-area: chart;
    }
    #other {
        grid-area: more;
    }
</style>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<script>
    const chartElement = document.getElementById('chart');
    new Chart(chartElement, {
        type: 'line',
        responsive: true,
        maintainAspectRatio: false,
        data: {
            labels: ['January', 'February', 'March', 'April', 'May', 'June'],
            datasets: [{
                label: 'Sample Data',
                data: [17, 15, 8, 2, 12, 19]
            }]
        }
    });
</script>

Here are screenshots with grid lines turned on in the developer tools to illustrate the issue.

Normal viewport width:
The chart when the viewport is at a normal width, as expected.

Wider viewport:
The chart when the viewport is widened, the chart does not resize.

If I add width: 100% to the container, there is no difference in the resizing. If I add width: 100% to the canvas element, the chart becomes distorted (like when you resize an image horizontally but not vertically). I also added the following in an attempt to force the resize event to fire on the chart upon window resize, with no difference:

window.addEventListener('beforeprint', () => {
    chartElement.resize();
})

I’ve been scouring the Chart.js documentation for other possible solutions, but haven’t found any. Is there a way to make the chart redraw to take up the full grid cell width when the viewport is resized? Thanks in advance!

My website keeps crashing when I try to upload an image [closed]

I am making a resume builder in Next.js and I am using React-Hook-Form with Zod and Shadcn.

"use client"

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { personalInfoSchema } from "@/lib/formValidations";
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useEffect, useRef } from "react";
import { useForm } from "react-hook-form";

const PersonalInforForm = ({ resumeData = {}, setResumeData = "" }) => {
  const form = useForm({
    resolver: zodResolver(personalInfoSchema),
    defaultValues: {
      firstName: resumeData.firstName || "",
      lastName: resumeData.lastName || "",
      jobTitle: resumeData.jobTitle || "",
      city: resumeData.city || "",
      country: resumeData.country || "",
      phone: resumeData.phone || "",
      email: resumeData.email || "",
    },
  });

  useEffect(() => {
    const { unsubscribe } = form.watch(async () => {
      const isValid = await form.trigger();

      if (!isValid) return;

      //update resume
    });

    return unsubscribe;
  }, [form]);

  const photoInputRef = useRef(null);

  return (
    <div className="mx-autp max-w-xl space-y-6">
      <div className="space-y-1.5 text-center">
        <h2 className="text-2xl font-semibold">Personal info</h2>
        <p className="text-sm text-muted-foreground">Tell us about yourself.</p>
      </div>
      <Form {...form}>
        <form className="space-y-3">
          <FormField
            control={form.control}
            name="photo"
            render={({ field: { value, ...fieldValues } }) => (
              <FormItem>
                <FormLabel>Your photo</FormLabel>
                <div className="flex items-center gap-2">
                  <FormControl>
                    <Input
                      {...fieldValues}
                      type="file"
                      accept="image/*"
                      onChange={(e) => {
                        const file = e.target.files[0];
                        fieldValues.onChange(file);
                      }}
                      ref={photoInputRef}
                    />
                  </FormControl>
                  <Button
                    variant="secondary"
                    type="button"
                    onClick={() => {
                      fieldValues.onChange(null);
                      if (photoInputRef.current) {
                        photoInputRef.current.value = "";
                      }
                    }}
                  >
                    Remove
                  </Button>
                </div>
                <FormMessage />
              </FormItem>
            )}
          />
        </form>
      </Form>
    </div>
  );
};

export default PersonalInforForm;

I have even tried some copying some people over at Youtube but it didn’t work.

When I try to upload even much smaller images than the limit (literally a few 100 kbs) the page gets stuck. I don’t know what to do. Is this due to any hardware issues?

How do I extract forum post content from AoPS when it’s visible in the browser but missing from driver.page_source?

I am not a web developer but an olympiad instructor with no coding experience who is using chatgpt to scrape math problems for personal use. I have been stuck at the following problem for 10+ hours. The following content is the summary produced by AI and the broad bits are right but I dont get the technical bits.

Details: I’m trying to scrape forum post content from Art of Problem Solving (AoPS) — specifically from threads like this one:

https://artofproblemsolving.com/community/c6h86541p504698

In the browser, the full post is visible — it includes a clean math problem and a solution.

But when I try to extract content using Python + Selenium, the post is completely missing from driver.page_source.

What I’ve tried:
• Selenium waits:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.CLASS_NAME, "post-text"))
)
```Also tried cmty-post-content, post_message, and others.

• Scrolling:

driver.execute_script(“window.scrollTo(0, document.body.scrollHeight)”)

• Looking for post by ID:
The URL includes p504698, so I tried:

soup.find(“div”, id=”post_504698″)

Nothing found.

• Content-based div scraping:
Scanned all <div>s looking for math-like keywords (“Let”, “prove”, “=”). This sometimes works — but not consistently.

•JS evaluation:

driver.execute_script(“return document.body.innerText”)

Still misses the post.


What’s likely happening?
It seems AoPS injects the post after page load, possibly using deferred JavaScript or Shadow DOM. The post isn’t part of the initial DOM snapshot, and doesn’t show up in driver.page_source or standard find_element() lookups.


Relevant threads I’ve consulted:
- [Content not in page source (57956324)](https://stackoverflow.com/questions/57956324/not-able-to-scrape-dynamic-content-using-selenium-or-beautifulsoup)
- [Use JavaScript extraction (63314833)](https://stackoverflow.com/questions/63314833/how-to-scrape-data-from-a-dynamic-website-with-selenium)
- [General dynamic scraping (36779288)](https://stackoverflow.com/questions/36779288/scraping-dynamic-content-through-selenium)
- [Get dynamic content with JS (15456872)](https://stackoverflow.com/questions/15456872/getting-dynamic-content-from-a-website-through-javascript)

None of them seem to solve this edge case.

What I’m looking for:
• How can I reliably extract just the actual forum post text, ideally as div.text or similar?
• Should I be targeting a particular JS variable, event trigger, or DOM mutation?
• Do I need to use MutationObserver or intercept XHR calls?

Would love help from anyone who’s scraped AoPS, Discourse, or similar JS-heavy forum platforms.

Optimizing weather animations (snow/rain/stars) for mobile – alternatives to 100+ CSS animated elements? [closed]

I’m building a weather-themed portfolio with interactive animations that must maintain performance on mobile devices (≥ iPhone 8/Android 9). While I’ve made optimizations, I’m hitting performance limits with the current CSS-based approach.

  1. Frame drops (especially on older devices like iPhone 8)

  2. High battery consumption

  3. Jitter during scrolling

<!-- Simplified snowflake example -->
<div class="snowflake"></div>
<style>
  .snowflake {
    position: fixed;
    width: 8px;
    height: 8px;
    background: white;
    border-radius: 50%;
    animation: fall 8s linear infinite;
    will-change: transform;
  }
  @keyframes fall {
    to { transform: translateY(100vh) rotate(360deg); }
  }
</style>

Performance (Moto G5/Chrome):

  • 50 snowflakes: ~40fps, +150MB GPU memory

  • 60 raindrops + ripples: CPU ~25% sustained

  • 200 stars: Significant scroll jitter

What I’ve Tried:

  1. Reduced elements (100 → 50 snowflakes)

  2. Optimized with will-change: transform and translateZ(0)

  3. Implemented requestAnimationFrame for JS control

  4. Added prefers-reduced-motion support

Core Questions:

  1. Element Limits: Is there a recommended maximum number of concurrently animated elements for mobile?

  2. Technology Choice: At what point should I switch from CSS to:

    • Canvas (requestAnimationFrame + object pooling)

    • SVG (SMIL animations)

    • WebGL (PixiJS/Three.js)

  3. Scroll Performance: Best practices to prevent jitter when many elements are animating during scroll?

Constraints:

  • Must maintain visual quality (snowflakes need individual movement)

  • Should work on mid-range mobile devices

  • Prefers-reduced-motion support required

Unselecting and other weird behaviors like tapping outside deselect sometimes when the app is tested on a real phone, but works in pc mobile size

This is very weird, when you select the same hour that you selected it should deselect it but it doesn’t work when tested on a real phone. When i test it in my pc using mobile size 290 width pixels works fine but then in a real phone it doesnt, to make it even more weird is that the problem happens in angular and remix but not in next.js. In next.js the feature works just fine. The logic is not the issue as I’ve already tested that in multiple ways and it works fine if the feature is next.js but the problem will arise in angular and remix.

Angular.

HTML:

<!--Third form--> 
<div class="form_outer_div">
    <div class="icon__wrapper_form">
        <p class="what_is_this_section_about">
            Operating Times
        </p>
        <button *ngIf="!thirdForm" (click)="openCloseThirdForm()" class="icon__styling__form">
            <lucide-angular [img]="ChevronDown" class="open_close_forms_icons"/>
        </button>

        <button *ngIf="thirdForm" (click)="openCloseThirdForm()" class="icon__styling__form">
            <lucide-angular [img]="ChevronUp" class="open_close_forms_icons"/>
        </button>
    </div>

    <div *ngIf="thirdForm" class="centering_grid_div_content_base_class_no_border">
        <div>
            <p class="gray_p">Choose Operating Time</p>
            <input class="input_fields" readonly/>
            <div class="centering_grid_div_content_base_class_no_border">
                <div class="flex_centering_no_border">

                    <div class="flex_centering_no_border">
                        <p class="small_gray_p">opening time</p>

                        <button *ngIf="!openingTimeSection" (click)="handleOpeningTimeSection()">
                            <i-lucide [img]="ChevronDown" />
                        </button>

                        <button *ngIf="openingTimeSection" (click)="handleOpeningTimeSection()">
                            <i-lucide [img]="X" />
                        </button>
                    </div>

                    <div class="flex_centering_no_border">
                        <p class="small_gray_p">closing time</p>

                        <button *ngIf="!closingTimeSection" (click)="handleClosingTimeSection()">
                            <i-lucide [img]="ChevronDown"/>
                        </button>

                        <button *ngIf="closingTimeSection" (click)="handleClosingTimeSection()">
                            <i-lucide [img]="X"/>
                        </button>
                    </div>
                </div>

                <div *ngIf="openingTimeSection" class="blink_on_render div_flex_centering_with_border">
                    <!--Hour--> 
                    <div class="container_holding_certain_type_of_time">
                        <div class="small_text_red_color_within_div">
                            hh
                        </div>
                        <div class="time_div_content">
                            <div *ngFor="let hour of hours" class="loop_content_div" (click)="handleChooseOpeningHour(hour)"
                                [ngClass]="{'set_background_bg_sky_600': openHourSelected === hour}"
                            >
                                {{hour}}
                            </div>
                        </div>
                    </div>
                </div>

            </div>
        </div>
    </div>
</div>

Component:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LucideAngularModule, ChevronDown, ChevronUp, X } from 'lucide-angular';

@Component({
  selector: 'app-create-operating-times',
  imports: [LucideAngularModule, CommonModule],
  templateUrl: './create-operating-times.component.html',
  styleUrl: './create-operating-times.component.scss'
})
export class CreateOperatingTimesComponent {

  //Icons
  readonly ChevronDown =  ChevronDown;
  readonly ChevronUp = ChevronUp;
  readonly X = X;

  //Loop data for time
  readonly hours = Array.from({length: 24}, (_, h) => h.toString().padStart(2, '0'));
  readonly minutes = Array.from({length: 60}, (_, m) => m.toString().padStart(2, '0'));

  //third form related
  thirdForm: boolean = false;
  openingTimeSection: boolean = false;
  closingTimeSection: boolean = false;
  openHourSelected: string = "";


  openCloseThirdForm(){
    if(!this.thirdForm){
      this.thirdForm = true;
      this.openingTimeSection = true;
      this.closingTimeSection = false;
    }else{
      this.thirdForm = false;
      this.openingTimeSection = false;
      this.closingTimeSection = false;
    }
  }

  handleOpeningTimeSection(){
    if(!this.openingTimeSection){
      this.openingTimeSection = true;
      this.closingTimeSection = false;
    }else{
      this.openingTimeSection = false;
    }
  }

  handleChooseOpeningHour(hour: string){
    if(this.openHourSelected === hour){
      this.openHourSelected = ""
    }else{
      this.openHourSelected = hour;
    }
  }

  handleClosingTimeSection(){
    if(!this.closingTimeSection){
      this.openingTimeSection = false;
      this.closingTimeSection = true;
    }else{
      this.closingTimeSection = false;
    }
  }
}

SCSS:

html {
    touch-action: manipulation;
    width: 100%;
}

.container{
    max-width: 100%;
    margin-left: auto;
    margin-right: auto;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;

}

.form_outer_div{
    border-width: 1px; /* Tailwind's 'border' */
    border-radius: 0.375rem; /* Tailwind's 'rounded-md' */
    border-color: #1e3a8a; /* Tailwind's 'border-blue-900' */
    padding: 0.5rem; /* Tailwind's 'p-2' */
    margin: 0; /* Tailwind's 'mb-2' */
    display: grid; /* Tailwind's 'grid' */
    width: 100%; /* Tailwind's 'w-full' */
    border: 2px solid #1e3a8a;;
}

.flex_centering_no_border{
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
}

.centering_grid_div_content_base_class{
    display: grid;
    border-radius: 0.375rem;
    border: 1px solid #1e40af;
    text-align: center;
    justify-items: center;
    padding: 0.5rem;
}

.centering_grid_div_content_base_class_no_border{
    display: grid;
    border-radius: 0.375rem;
    text-align: center;
    justify-items: center;
    padding: 0.5rem;
}

.div_flex_centering_with_border{
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    border: 2px solid #1e40af; /* border-blue-900 */
    border-radius: 0.375rem; /* rounded-md */
    padding-top: 0.25rem; /* pt-1 */
    padding-bottom: 0.25rem; /* pb-1 */
}

.input_fields, .input_fields::placeholder{
    border-width: 1px;
    border-style: solid;
    border-color: #1e3a8a; /* Tailwind's border-blue-900 */
    border-radius: 0.375rem; /* rounded-md */
    text-align: center;
    margin: 0; /* mb-2 */
    caret-color: transparent;
    height: 2rem;
}

.small_text_red_color_within_div{
    display: flex;
    font-weight: bold;
    justify-content: center;
    color: #ef4444;
    font-size: 16px;
}

.small_gray_p{
    color: #6b7280; /* Tailwind's gray-500 */
    font-size: 1rem; /* Tailwind's text-xs */
    font-weight: bold;
}

.gray_p{
    font-size: 1.20rem; /* Tailwind's 'text-base' */
    color: #6b7280;  /* Tailwind's 'text-gray-500' */
    font-weight: bold;
}

.icon__wrapper_form{
    display: flex;
    justify-content: space-between;
}

.icon__styling__form{
    margin-bottom: 2px;
    padding-top: 0px;
}

::ng-deep .open_close_forms_icons svg {
    width: 32px !important;
    height: 32px !important;
    stroke-width: 3;
    stroke: #1e3a8a;
}

.div_what_is_section_about_and_icon{
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
}

.p_operating_times{
    color: #6b7280;  /* Equivalent to text-gray-500 in Tailwind */
    font-size: 0.75rem;  /* Equivalent to text-xs in Tailwind */
}

.container_holding_certain_type_of_time{
    @extend .centering_grid_div_content_base_class;
    height: 15rem;
    border-width: 1px;
    border-style: solid;
    border-color: #e5e7eb; /* Default border color in Tailwind */
}

.what_is_this_section_about{
    color: #3B82F6; /*blue-500*/
    margin-bottom: 2px;
    font-weight: bold;
    font-size: 1.35rem; 
}

.time_labeling{
    display: flex;
    color: #f87171; /* Tailwind's red-500 color */
    font-weight: bold;
    font-size: 0.875rem; /* 14px */
    justify-content: center
}

.loop_content_div{
    border-top: 1px solid #3b82f6;
    padding: 0.25rem; /* 4px */
    border-bottom: 1px solid #3b82f6; /* Tailwind's blue-500 color */
    cursor: pointer;
    width: 48px;
}

.loop_content_div:hover{
    @extend .hover_effect_sky_600;
}

.time_div_content{
    -webkit-tap-highlight-color: transparent; /* Hide tap highlight */
    touch-action: manipulation; /* Reduce delay on Android */
    display: grid;
    border-radius: 0.375rem;
    border-color: #1e3a8a; /* Tailwind's blue-900 color */
    padding: 0.25rem; /* 4px */
    height: 100%;
    overflow-y: auto;
    width: 100%;
     /* Mobile interaction helpers */
    -webkit-overflow-scrolling: touch;
    touch-action: manipulation;
    pointer-events: auto;
}

/* xs */
@media (min-width: 475px) {
    .container {
        max-width: 475px;
    }
}

/* sm */
@media (min-width: 640px) {
    .container {
        max-width: 640px;
    }
}

/* md */
@media (min-width: 768px) {
    .container {
        max-width: 768px;
    }
}

/* lg */
@media (min-width: 1024px) {
    .container {
        max-width: 1024px;
    }
}

/* lx */
@media (min-width: 1280px) {
    .container {
        max-width: 1280px;
    }
}

/* 2xl*/
@media (min-width: 1536px) {
    .container {
        max-width: 1536px;
    }
}

//Conditional styling
.set_background_bg_sky_600{
    background-color: #0284c7;
}

//Hover effects
.hover_effect_sky_600{
    background-color: #0284c7;
}

/* Animations and transition */

@keyframes blinkFade {
    0% {
        opacity: 0;
    }
    10%{
        opacity: 1;
    }
}

.blink_on_render{
    animation: blinkFade 0.2s ease-in-out;
}

As i said this is very weird because creating the same exact feature in next.js works just fine in a real phone but the problem i described before happens in angular and remix. When i tested the feature in the browser using mobile size it worked but not in a real phone. Thanks for helping

WebAuthn passkey prompt not appearing in Firefox 137 on Windows 11

I’m working on a login flow that uses WebAuthn passkey authentication on the frontend. The implementation works fine in Chrome and Edge, but the passkey prompt does not appear at all in Firefox version 137, running on Windows 11 24H2.

Here’s the relevant part of the JavaScript code:

navigator.credentials.get({
  publicKey: {
    challenge: Uint8Array.from(window.atob(challenge), c => c.charCodeAt(0)),
    allowCredentials: [{
      id: Uint8Array.from(window.atob(credentialId), c => c.charCodeAt(0)),
      type: "public-key"
    }],
    timeout: 60000,
    userVerification: "preferred"
  }
}).then(assertion => {
  // Handle successful assertion
}).catch(err => {
  console.error('WebAuthn failed:', err);
});

Is there an extra configuration needed in Firefox for passkey-based WebAuthn to work on Windows? Or is this a known issue in Firefox 137?

Thanks in advance for any help!

Detect single word text selection using javascript on android

I want to detect when a user finishes making a text selection on android, using cordova and javascript. This is possible for multi-word selections (see the gist by parthibeyond, added at the end of the question for completeness), but it does not work for a single word selection. When initiating a selection by touching and holding a word the following events are fired in sequence:

  1. selectstart
  2. contextmenu
  3. selectionchange
  4. touchcancel

At this point, the user can either lift their finger to select only the initial word, or move it while still touching the screen to select additional words. Is there any way to detect the first case of single word selection? The touchcancel event prevents the touchend event from being fired, and a timer is not useful since the user may hold the finger still for an arbitrary amount of time before moving it for multi-word selection.

The OS indicates the removal of the finger, indicating the end of the selection, for both single and multi-word selection by adding “grab handles” at either end of the selected text:

Finger touching screen (context menu suppressed for clarity)

Finger touching screen

Finger removed from screen

Finger removed from screen

Is there any way to get this event in javascript?

The only workaround I can find is to call event.preventDefault() on touchstart but this removes all text selection and scrolling functionality.

Code for multi-word selection end event, credit parthibeyond

function customisedSelectListener(el = document, callbackFn) {
  // events to be listened - 'selectstart', 'selectionchange', 'contextmenu'
  let eventSequence = [];

  el.addEventListener('selectstart', function() {
    eventSequence = [];
    eventSequence.unshift(event.type);
  })

  el.addEventListener('selectionchange', function() {
    if (eventSequence[0] != 'selectionchange') {
      eventSequence.unshift(event.type);
    }
  })

  el.addEventListener('contextmenu', function() {
    eventSequence.unshift(event.type);
    if (eventSequence[1] == 'selectionchange') {
      callbackFn.call();
    }
  })
}

Usage:

customisedSelectListener(document, function (){
  alert('Text Selection Completed !!');
})