CSS with react-text-transition – how to align text properly for all browsers

On my landing page, I have some static text which is then followed by a word which I want to change every couple of seconds with transition. I decided to use react-text-transition for this. My issue is that I can’t seem to align those to look properly and consistently across browsers.

Currently this is the code:

<Box sx={{display: "inline-block", mt: {xs: 5, sm: 8}, mb: 1}}>
    <Typography sx={{display: "ruby", typography: { xs: "h4", sm: "h3"}}}>Blah blah blah in your
        <Box sx={{width: '10px'}} />
        {<Box sx={{ display: "inline", minWidth: {xs: `${18 * ctaChangingTextListLongestWordLength + 5}px`, sm: `${24 * ctaChangingTextListLongestWordLength + 10}px`}}}><IntervalChangingText textList={ctaChangingTextList} interval={4000}/></Box>}
    </Typography>
</Box>

and the IntervalChangingText component:

import { useEffect, useState } from "react"
import TextTransition, { presets } from 'react-text-transition';

export default function IntervalChangingText(props) {
    const {textList, interval} = props
    const [textListIndex, setTextListIndex] = useState(0)

    useEffect(() => {
        const intervalId = setInterval(() => {
            setTextListIndex(textListIndex + 1)
        }, interval);
    
        return () => clearInterval(intervalId);
    }, [textListIndex]);

    return (
        <TextTransition springConfig={presets.slow}>{textList[textListIndex % textList.length]}</TextTransition>
    )
}

On Chrome, things look good aka like this:

-----------------------------
|Blah blah blah              |
|in your [text that changes] |
-----------------------------

However on Safari, they do not:

-----------------------------
|Blah blah blah              |
|in your                     |
|[text that changes]         |
-----------------------------

And in Firefox it’s even worse, the text is all in one line and leaves its box:

-----------------------------
|Blah blah blah in your [text| that changes]
-----------------------------

I suspect the issue is with the display: “ruby” but I don’t know how to make this work consistently with css across all browsers. Any ideas?

D3.js Bug in the resize function

I am in the process of writing a resize function for my chart. So far everything works quite well.

However, I noticed the following bug when using the resize function.

After I have rendered the chart and then resize the window, my heatmap chart extends some “px” to the right. This causes the chart to be cut off on the right side.

Can someone please explain why this bug occurs and how I can fix it?

I would be grateful for any advice.

enter image description here

 //Datasource
            const data1= [
                {id: 1,group:"A",variable:"v1",value: 98},{id: 2,group:"A",variable:"v2",value:95},
                {id: 3,group:"A",variable:"v3",value: 22},{id: 4,group:"A",variable:"v4",value:14},
                {id: 11,group:"B",variable:"v1",value: 37},{id: 12,group:"B",variable:"v2",value:50},
                {id: 13,group:"B",variable:"v3",value: 81},{id: 14,group:"B",variable:"v4",value:79}, 
                {id: 21,group:"C",variable:"v1",value: 96},{id: 22,group:"C",variable:"v2",value:13},
                {id: 23,group:"C",variable:"v3",value: 98},{id: 24,group:"C",variable:"v4",value:10},
                {id: 31,group:"D",variable:"v1",value: 75},{id: 32,group:"D",variable:"v2",value:18},
                {id: 33,group:"D",variable:"v3",value: 92},{id: 34,group:"D",variable:"v4",value:43},
                {id: 41,group:"E",variable:"v1",value: 44},{id: 42,group:"E",variable:"v2",value:29},
                {id: 43,group:"E",variable:"v3",value: 58},{id: 44,group:"E",variable:"v4",value:55},
                {id: 51,group:"F",variable:"v1",value: 35},{id: 52,group:"F",variable:"v2",value:80},
                {id: 53,group:"F",variable:"v3",value: 8},{id: 54,group:"F",variable:"v4",value:46}
            ];

            var i_region_static_id = "heatmap"
            var i_height = 450;
            var margin = {top: 0, right: 50, bottom: 25, left: 70}; 
            var width_client = document.body.clientWidth;
            var height_client = document.body.clientHeight;

            const innerWidth = width_client - margin.left - margin.right;
            const innerHeight = i_height - margin.top - margin.bottom; 

            var colorMidValue = 20;

            const labelGroup = "X-Label"
            const labelVars = "Y-Label"
            const color_start = "#f9f9f9"
            const colorEnd = "#6e69b3"
            const colorMid = "#bd326c"

            // Funktion zum Aufrunden der Y-Axis auf 5
            function updateColorAxis(a,b){
                if(b%5 !== 0){
                    b = b+(5-(b%5)); 
                }
                return [a,b];
            }
        
            // Funktion zum Berechnen der Anzahl der Ticks 
            function setColorAxisTicks(a,b){
                if(b%5 === 0){
                    return b/5;
                }
                return b/10;
            }

            // Returns Black/White depending on node color
            function calculateContrast(values){
                var rgb = values.match(/d+/g);
                // Wenn rgb leer, dann das erste sonst das zweite
                const brightness = (rgb != undefined) ? Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) /1000) : 0;
                return brightness < 150 ? 'white' : 'black'   
            }
                
            
            //-----------------------------------------------------------------------------------
            // Anfügen von svg Elementen zu #heatmap
            const svg = d3.select('#svg_heatmap')
                                // Attribute svg
                                .attr("height", innerHeight)
                                .attr("width", width_client + margin.left + margin.right)

            const g_area = d3.select("#svg_" + i_region_static_id)                           
                                // Anfügen einer Gruppe an das SVG Element
                                .append("g")
                                    .attr("id", i_region_static_id + "_area")
                                    .attr("transform", `translate(${margin.left},${margin.top})`);

            const g_nodes = g_area
                                .append("g")
                                .attr("id", i_region_static_id + "_nodes")
            //-----------------------------------------------------------------------------------
            // X-Skalierung
            const x_scale = d3.scaleBand()
                        .range([0, innerWidth])
                        .padding(0.01);

            const xAxisCall = d3.axisBottom(x_scale)

            // X-Achse erstellen
            const xAxis = g_area
                                .append("g")
                                .attr("id", i_region_static_id + "_x_axis")

                        // Einfügen xAxis Values    
                        xAxis.append("g")
                                .attr("id", i_region_static_id + "_x_axis_value")
                                .attr("class", "x axis")
                                .attr("transform", `translate(0, ${innerHeight})`)

                        // Einfügen xAxis Label
                        xAxis.append("text")
                                .attr("id", i_region_static_id + "_x_axis_label")
                                .attr("class", "x label")
                                .attr("x", innerWidth / 2)
                                .attr("y", innerHeight + 50)
                                .attr("text-anchor", "middle")
                                .text(labelGroup);

            
            //-----------------------------------------------------------------------------------
            // Y-Skalierung
            const y_scale = d3.scaleBand()
                        .range([innerHeight, 0])
                        .padding(0.03);
            
            const yAxisCall = d3.axisLeft(y_scale)
                        
            // Y-Achse erstellen
            const yAxis = g_area
                            .append("g")
                                .attr("id", i_region_static_id + "_y_axis")

                        // Einfügen yAxis Value
                        yAxis.append("g")
                            .attr("id", i_region_static_id + "_y_axis_value")
                            .attr("class", "y axis")

                        // Einfügen yAxis Label
                        yAxis.append("text")
                                .attr("id", i_region_static_id + "_y_axis_label")
                                .attr("class", "y label")
                                .attr("y", - 45)                // Y ist hierbei die horizontale ausrichtung
                                .attr("x", - innerHeight / 2)   // X ist hierbei die vertikale ausrichtung
                                .attr("transform", "rotate(-90)")
                                .attr("text-anchor", "middle")
                                .text(labelVars)

            //-----------------------------------------------------------------------------------     
            // Erstelle yColorscale
            const yColorscale = d3.scaleLinear()
                                    .range([innerHeight - 70, 0]);

            // ColorScale Legend
            const g_colorScale = g_area
                                    .append("g")
                                        .attr("id", i_region_static_id + "_colorLegend")

            // Einfügen der Color Achse
            g_colorScale.append("g")
                            .attr("id" , i_region_static_id + "_colorscale_axis")
                            .attr("transform", `translate(${innerWidth + 27},${35})`)
            
            // Einfügen des Farbbalkens
            g_colorScale.append("rect")
                            .attr("id", i_region_static_id + "_colorScale_legend")
                            .attr("x", innerWidth + 15)
                            .attr("y", 35)
                            .attr("rx", "4px")
                            .attr("width", 12)
                            .attr("height", innerHeight - 70)
                            .attr("stroke", "black")
                            .attr("stroke-width", "0.5px")
                            .attr("text-anchor", "middle")
                            .style("fill", "url(#"+ i_region_static_id + "_grad)");
 
            const g_def = g_colorScale.append("defs")
                                        .append("linearGradient")
                                            .attr("id", i_region_static_id + "_grad")
                                            .attr("x1", "0%")
                                            .attr("x2", "0%")
                                            .attr("y1", "0%")
                                            .attr("y2", "100%");
            
            //-----------------------------------------------------------------------------------               
            // Funktion für das Create, Update or Delete der D3 nodes
            function update_heatmap(data){
                const myId = data.map(function(d){return d.id});
                const myGroups = data.map(function(d){return d.group});
                const myVars = data.map(function(d){return d.variable});
                const value = data.map(function(d){return d.value});
                const extent = d3.extent(data, function(d){;return d.value}); 
                var colorArrayValue = updateColorAxis(extent[0], extent[1]);
                    colorArrayValue.push(colorMidValue);
                    colorArrayValue.sort(function(a, b){return a - b});

                const myColor = d3.scaleLinear()
                                .range([color_start, colorMid, colorEnd])
                                .domain(colorArrayValue)

                const GradColors = [myColor(updateColorAxis(extent[0], extent[1])[1]), myColor(colorMidValue), myColor(updateColorAxis(extent[0], extent[1])[0])];
                

                //----------------------------------------------------------------------------------- 
                // Dynamisches Update der X-Achse
                x_scale.domain(myGroups)
                const xAxisGroup = xAxis.select("#" + i_region_static_id + "_x_axis_value")
                                                .call(xAxisCall);

                //----------------------------------------------------------------------------------- 
                // Dynamisches Update der Y-Achse
                y_scale.domain(myVars)
                const yAxisGroup = yAxis.select("#" + i_region_static_id + "_y_axis_value")
                                                .call(yAxisCall);

                //-----------------------------------------------------------------------------------  
                // ColorScale Legend
                yColorscale.domain(updateColorAxis(extent[0], extent[1]))

                const yAxisColorCall = d3.axisRight(yColorscale)
                                                .ticks(setColorAxisTicks(updateColorAxis(extent[0], extent[1])[0], updateColorAxis(extent[0], extent[1])[1]))
                
                const yAxisColorGroup = g_colorScale.select("#" + i_region_static_id + "_colorscale_axis")
                                                .call(yAxisColorCall);

                g_def.selectAll(i_region_static_id + "stop")
                                                .data(GradColors)
                                                .enter()
                                                .append("stop")
                                                .style("stop-color", function(d){return d;})
                                                .attr("offset", function(d,i){
                                                    return 100 * (i / (GradColors.length - 1)) + "%";
                })

                //-----------------------------------------------------------------------------------  
                // Create, Delete and Update
                const rect_nodes = g_nodes.selectAll("." + i_region_static_id + "_node_grp")
                                                .data(data)

                const rect_node_grp = rect_nodes.enter()
                                                .append("g")
                                                /*each() für Create Sektion*/
                                                /*Mit der Each() wird alles in der gruppe geupdated*/
                                                .each(function(d, i) {
                                                //Append Elemente an g
                                                    d3.select(this).append('rect')   
                                                        .style("fill", function(d) {return myColor(d.value)}) 
                                                    d3.select(this).append('text')
                                                })
                                                .merge(rect_nodes)
                                                .attr("class", i_region_static_id + "_node_grp")
                                                //jedes Rect eine ID zu ordnen
                                                .attr("id", function (d){return i_region_static_id + "_node_grp_" + d.id})
                                                .attr("group", function(d) {return d.group})
                                                .attr("variable", function(d) {return d.variable})
                                                .attr("value", function(d) {return d.value})
                                                /*each() für Update Sektion*/
                                                .each(function(d, i) {
                                                    // Update meine Elemente in g tag
                                                    d3.select(this).select('rect')
                                                        .attr("class", i_region_static_id + "_node")
                                                        .transition()
                                                        .delay(50)
                                                        .attr("x", function(d) {return x_scale(d.group)})
                                                        .attr("y", function(d) {return y_scale(d.variable)})
                                                        .attr("width", x_scale.bandwidth())
                                                        .attr("height", y_scale.bandwidth())
                                                        .style("fill", function(d) {return myColor(d.value)})

                                                    d3.select(this).select('text').attr("class", i_region_static_id + "_label")
                                                        .transition()
                                                        .delay(50)
                                                        .attr("x", function(d) {return x_scale(d.group)})
                                                        .attr("y", function(d) { return y_scale(d.variable)})
                                                        .attr("dx", x_scale.bandwidth() / 2)
                                                        .attr("dy", y_scale.bandwidth() / 2)
                                                        .attr("text-anchor", "middle")
                                                        .attr("dominant-baseline", "central")
                                                        .attr("fill", function(d){return calculateContrast(myColor(d.value))})
                                                        .style("font-size", "14px")
                                                        .style("font-family", "sans-serif")
                                                        .text(function(d) {return (d.value)})
                                                    })

                rect_nodes.exit().remove()
            }

            function resize() {
                var innerWidth = parseInt(d3.select("#svg_" + i_region_static_id).style("width")) - margin.left - margin.right,
                    innerHeight

                if(parseInt(d3.select("#svg_" + i_region_static_id).style("height")) < i_height){
                    innerHeight = parseInt(d3.select("#svg_heatmap").style("height")) + margin.top + margin.bottom;
                }else{
                    innerHeight = i_height - margin.top - margin.bottom;
                }
                
                // SVG Dimension
                svg.attr("height", innerHeight)
                    .attr("width", innerWidth)

                // Aktualisieren der Range vom scale mit neuer breite und Höhe
                x_scale.range([0, innerWidth], 0.1);
                y_scale.range([innerHeight, 0]);

                // Position der X-Achse und X-Label
                xAxis
                    .call(xAxisCall)
                    .select("#" + i_region_static_id + "_x_axis_label")
                            .attr("x", innerWidth / 2)
                            .attr("y", innerHeight + 50);
                
                // Position der Y-Achse und X-Label
                yAxis
                    .call(yAxisCall)
                    .attr("transform", "translate(0," + 0 + ")")
                    .select("#" + i_region_static_id + "_y_axis_label")
                        .attr("y", - 45)
                        .attr("x", - innerHeight / 2)

                // Position ColorSkale Farb-Balken 
                g_colorScale
                        .select("#" + i_region_static_id + "_colorScale_legend")
                            .attr("x", innerWidth + 15)
                            .attr("y", 35)
                            .attr("height", innerHeight - 70)
                    
                // Position ColorSkale Achse 
                g_colorScale
                        .select("#" + i_region_static_id + "_colorscale_axis")
                            .attr("transform", `translate(${innerWidth + 27},${35})`)
    
                // D3 Kästchen dimension wird neu kalkuliert
                g_nodes.selectAll("." + i_region_static_id + "_node")
                        .attr("x", function(d) {return x_scale(d.group)})
                        .attr("y", function(d) {return y_scale(d.variable)})
                        .attr("width", x_scale.bandwidth())
                        .attr("height", y_scale.bandwidth())
                        
                // D3 Kästchen Label wir neu Positioniert
                g_nodes.selectAll("." + i_region_static_id + "_label")
                    .attr("x", function(d) {return x_scale(d.group)})
                    .attr("y", function(d) { return y_scale(d.variable)})
                    .attr("dx", x_scale.bandwidth() / 2)
                    .attr("dy", y_scale.bandwidth() / 2)
            };
            //-----------------------------------------------------------------------------------               
            d3.select(window).on('resize', resize);

            update_heatmap(data1)
        <style>
            .label{
                fill: rgba(0, 0, 0, 0.65);
                font-size: 18px;
                font-weight: bold;
                font-family: sans-serif;
            }

            .axis{
                color: rgba(0, 0, 0, 0.65);
                font-size: 16px;
                font-family: arial;
            }

            #svg_heatmap {
                width: 100%;
                height: 100%;
                position: absolute;
            } 
<html>             
    <head>              
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title> 1.Grundgerüst </title>
    </head>
    <body>             
        <svg id="svg_heatmap"></svg>
        <script src="https://d3js.org/d3.v7.js" charset="utf-8"></script>
        <script src="https://unpkg.com/d3fc" charset="utf-8"> </script> 
    </body>
</html>

How to persist auth across app refresh in React Native app

I am using React Native / Expo to build an app with auth. I can log in fine but when I refresh the app, the auth is lost and I need to log in again.

My firebase config looks like this:

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: "<key>",
  authDomain: "<name>.firebaseapp.com",
  projectId: "<id>",
  storageBucket: "<id>.appspot.com",
  messagingSenderId: "<sender_id>",
  appId: "<app_id>",
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);

export { auth };

I have a self hosted log in form in my app that is as so

import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';

const LoginScreen = () => {
  const auth = getAuth();
  
  const handleLogin = useCallback(async (email: string, password: string) => {
    const user = await signInWithEmailAndPassword(auth, email, password);
  }, [auth]);

  return (
    <LoginForm onLogin={handleLogin} />
  )
}

The log in works fine and I can retrieve the users details and even see auth state change in this useEffect

useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      console.log("Auth state changed:", user);
    });
  
    return unsubscribe;
  }, [auth]);

But when I refresh the app, auth is === null and I need to log in again.

Apparently as of firebase >v9 it should auto persist the auth so I shouldn’t need to configure anything else?

How can I make my RN app have persisted log in using Firebase?

Package versions:

"firebase": "^11.3.1",
"react": "18.3.1",
"expo": "~52.0.31"

How to Use AJAX in WordPress Widget on Admin Backend only?

I’m trying to create a simple WordPress widget for AI text generation. The widget should allow the user to type a prompt, set a word limit, and click a “Generate Text” button. I want to use the OpenAI API to generate text based on the prompt and word limit. However, I’m facing an issue with how the widget behaves depending on where it’s loaded.

Problem:

The AI text generation works fine when all the options (form) are loaded on the WordPress front page (working example in CODE-1 below). But I want the widget to only be available in the backend (as part of the widget options).

So, my goal is to make the AI text generator available only in the backend for editing widgets, and have the API generate text and insert it into the widget’s text area. The main issue is that AJAX doesn’t seem to work properly within widget options, and I’m struggling to find a solution.

What I want to achieve:

  • Create a widget in the WordPress backend.
  • Use the OpenAI API to generate text based on user input (prompt and
    word limit).
  • Insert the generated text into the widget text area.

Questions:

How can I make AJAX work properly inside the WordPress widget options in the backend?
Is there a recommended approach for integrating OpenAI API text generation with a WordPress widget in the backend?
I’m not sure what else to check or why API requests can’t be handled through the backend widget options. I tested AI text generation on a backend page (not widgets), and it works fine there. The issue is only with the widgets, and I can’t figure out why it’s not working. There are no errors in the console, nothing—it just doesn’t work in the widget.

Any help would be greatly appreciated!

CODE-1 (works OK if loaded as front-end widget):

<?php
/*
Plugin Name: OpenAI Widget
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        ?>
        <div class="openai-widget">
            <label for="openai_prompt">Prompt:</label>
            <textarea id="openai_prompt" rows="3"></textarea>

            <label for="openai_word_limit">Word Limit:</label>
            <input type="number" id="openai_word_limit" value="100" min="1" max="500"/>

            <button id="openai_generate">Generate Text</button>

            <label for="openai_output">Output:</label>
            <textarea id="openai_output" rows="5" readonly></textarea>
        </div>

        <script>
document.addEventListener("DOMContentLoaded", function() {
    document.getElementById("openai_generate").addEventListener("click", function() {
        let prompt = document.getElementById("openai_prompt").value.trim();
        let wordLimit = parseInt(document.getElementById("openai_word_limit").value);

        if (prompt.length === 0) {
            alert("Please enter a prompt.");
            return;
        }

        fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: `action=openai_generate_text&prompt=${encodeURIComponent(prompt)}&word_limit=${wordLimit}`
        })
        .then(response => response.json())
        .then(data => {
            console.log(data);  // Debugging: log the response data

            // Check if the response is as expected
            if (data.success) {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = data.data.text || "No text generated.";  // Access text inside `data.data.text`
                } else {
                    console.error('Output textarea not found.');
                }
            } else {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = "Error: " + (data.message || "Unknown error");
                } else {
                    console.error('Output textarea not found.');
                }
            }
        })
        .catch(error => {
            console.error("Error:", error);
            const output = document.getElementById("openai_output");
            if (output) {
                output.value = "Error: Unable to reach API or process request.";
            }
        });
    });
});


        </script>
        <?php
        echo $args['after_widget'];
    }

    public function form($instance) {
        echo '<p>No settings required.</p>';
    }

    public function update($new_instance, $old_instance) {
        return $new_instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Debugging: Log the full response body to see the structure of the response
    error_log('OpenAI Response: ' . print_r($body, true));

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

CODE-2 (loaded in widgets backend options – not working):

<?php
/*
Plugin Name: OpenAI Widget - backend options
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        // On the front-end, just show the widget without any fields or outputs.
        echo $args['before_widget'];
        echo $args['after_widget'];
    }

    public function form($instance) {
        // Backend form
        $prompt = !empty($instance['prompt']) ? $instance['prompt'] : '';
        $word_limit = !empty($instance['word_limit']) ? $instance['word_limit'] : 100;
        ?>
        <p>
            <label for="<?php echo $this->get_field_id('prompt'); ?>"><?php _e('Prompt:'); ?></label>
            <textarea class="widefat" id="<?php echo $this->get_field_id('prompt'); ?>" name="<?php echo $this->get_field_name('prompt'); ?>" rows="3"><?php echo esc_attr($prompt); ?></textarea>
        </p>
        <p>
            <label for="<?php echo $this->get_field_id('word_limit'); ?>"><?php _e('Word Limit:'); ?></label>
            <input class="widefat" id="<?php echo $this->get_field_id('word_limit'); ?>" name="<?php echo $this->get_field_name('word_limit'); ?>" type="number" value="<?php echo esc_attr($word_limit); ?>" min="1" max="500" />
        </p>
        <p>
            <!-- Button in backend -->
            <button class="widefat" type="button" id="generate_openai_backend_text">Generate Text (Backend Only)</button>
        </p>
        <div id="openai_backend_output_area" style="display:none;">
            <h4>Generated Text:</h4>
            <textarea id="openai_backend_output" rows="5" readonly></textarea>
        </div>

        <?php
    }

    public function update($new_instance, $old_instance) {
        // Save the widget options
        $instance = array();
        $instance['prompt'] = !empty($new_instance['prompt']) ? sanitize_text_field($new_instance['prompt']) : '';
        $instance['word_limit'] = !empty($new_instance['word_limit']) ? intval($new_instance['word_limit']) : 100;
        return $instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

// Handle the AJAX request for text generation
function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

How to Use OpenAI API for Text Generation in WordPress Widget Backend with AJAX? (full plugin code)

I’m trying to create a simple WordPress widget for AI text generation. The widget should allow the user to type a prompt, set a word limit, and click a “Generate Text” button. I want to use the OpenAI API to generate text based on the prompt and word limit. However, I’m facing an issue with how the widget behaves depending on where it’s loaded.

Problem:

The AI text generation works fine when all the options (form) are loaded on the WordPress front page (working example in CODE-1 below). But I want the widget to only be available in the backend (as part of the widget options).

So, my goal is to make the AI text generator available only in the backend for editing widgets, and have the API generate text and insert it into the widget’s text area. The main issue is that AJAX doesn’t seem to work properly within widget options, and I’m struggling to find a solution.

What I want to achieve:

  • Create a widget in the WordPress backend.
  • Use the OpenAI API to generate text based on user input (prompt and
    word limit).
  • Insert the generated text into the widget text area.

Questions:

How can I make AJAX work properly inside the WordPress widget options in the backend?
Is there a recommended approach for integrating OpenAI API text generation with a WordPress widget in the backend?
I’m not sure what else to check or why API requests can’t be handled through the backend widget options. I tested AI text generation on a backend page (not widgets), and it works fine there. The issue is only with the widgets, and I can’t figure out why it’s not working. There are no errors in the console, nothing—it just doesn’t work in the widget.

Any help would be greatly appreciated!

CODE-1 (works OK if loaded as front-end widget):

<?php
/*
Plugin Name: OpenAI Widget
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        ?>
        <div class="openai-widget">
            <label for="openai_prompt">Prompt:</label>
            <textarea id="openai_prompt" rows="3"></textarea>

            <label for="openai_word_limit">Word Limit:</label>
            <input type="number" id="openai_word_limit" value="100" min="1" max="500"/>

            <button id="openai_generate">Generate Text</button>

            <label for="openai_output">Output:</label>
            <textarea id="openai_output" rows="5" readonly></textarea>
        </div>

        <script>
document.addEventListener("DOMContentLoaded", function() {
    document.getElementById("openai_generate").addEventListener("click", function() {
        let prompt = document.getElementById("openai_prompt").value.trim();
        let wordLimit = parseInt(document.getElementById("openai_word_limit").value);

        if (prompt.length === 0) {
            alert("Please enter a prompt.");
            return;
        }

        fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: `action=openai_generate_text&prompt=${encodeURIComponent(prompt)}&word_limit=${wordLimit}`
        })
        .then(response => response.json())
        .then(data => {
            console.log(data);  // Debugging: log the response data

            // Check if the response is as expected
            if (data.success) {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = data.data.text || "No text generated.";  // Access text inside `data.data.text`
                } else {
                    console.error('Output textarea not found.');
                }
            } else {
                const output = document.getElementById("openai_output");
                if (output) {
                    output.value = "Error: " + (data.message || "Unknown error");
                } else {
                    console.error('Output textarea not found.');
                }
            }
        })
        .catch(error => {
            console.error("Error:", error);
            const output = document.getElementById("openai_output");
            if (output) {
                output.value = "Error: Unable to reach API or process request.";
            }
        });
    });
});


        </script>
        <?php
        echo $args['after_widget'];
    }

    public function form($instance) {
        echo '<p>No settings required.</p>';
    }

    public function update($new_instance, $old_instance) {
        return $new_instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Debugging: Log the full response body to see the structure of the response
    error_log('OpenAI Response: ' . print_r($body, true));

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

CODE-2 (loaded in widgets backend options – not working):

<?php
/*
Plugin Name: OpenAI Widget - backend options
Description: A simple WordPress widget to generate text using OpenAI GPT-3.5 API.
Version: 1.0
Author: WP
*/

class OpenAI_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'openai_widget',
            __('OpenAI Text Generator', 'openai_widget_domain'),
            array('description' => __('Generate text using OpenAI GPT-3.5', 'openai_widget_domain'))
        );
    }

    public function widget($args, $instance) {
        // On the front-end, just show the widget without any fields or outputs.
        echo $args['before_widget'];
        echo $args['after_widget'];
    }

    public function form($instance) {
        // Backend form
        $prompt = !empty($instance['prompt']) ? $instance['prompt'] : '';
        $word_limit = !empty($instance['word_limit']) ? $instance['word_limit'] : 100;
        ?>
        <p>
            <label for="<?php echo $this->get_field_id('prompt'); ?>"><?php _e('Prompt:'); ?></label>
            <textarea class="widefat" id="<?php echo $this->get_field_id('prompt'); ?>" name="<?php echo $this->get_field_name('prompt'); ?>" rows="3"><?php echo esc_attr($prompt); ?></textarea>
        </p>
        <p>
            <label for="<?php echo $this->get_field_id('word_limit'); ?>"><?php _e('Word Limit:'); ?></label>
            <input class="widefat" id="<?php echo $this->get_field_id('word_limit'); ?>" name="<?php echo $this->get_field_name('word_limit'); ?>" type="number" value="<?php echo esc_attr($word_limit); ?>" min="1" max="500" />
        </p>
        <p>
            <!-- Button in backend -->
            <button class="widefat" type="button" id="generate_openai_backend_text">Generate Text (Backend Only)</button>
        </p>
        <div id="openai_backend_output_area" style="display:none;">
            <h4>Generated Text:</h4>
            <textarea id="openai_backend_output" rows="5" readonly></textarea>
        </div>

        <?php
    }

    public function update($new_instance, $old_instance) {
        // Save the widget options
        $instance = array();
        $instance['prompt'] = !empty($new_instance['prompt']) ? sanitize_text_field($new_instance['prompt']) : '';
        $instance['word_limit'] = !empty($new_instance['word_limit']) ? intval($new_instance['word_limit']) : 100;
        return $instance;
    }
}

function register_openai_widget() {
    register_widget('OpenAI_Widget');
}
add_action('widgets_init', 'register_openai_widget');

// Handle the AJAX request for text generation
function openai_generate_text() {
    if (!isset($_POST['prompt']) || !isset($_POST['word_limit'])) {
        wp_send_json_error(['message' => 'Invalid request']);
    }

    $api_key = 'MY-API-KEY'; // Replace with your OpenAI API key
    $prompt = sanitize_text_field($_POST['prompt']);
    $word_limit = max(1, min((int) $_POST['word_limit'], 500));

    // OpenAI tokens are ~4 per word, but we cap them to 2048 to prevent long responses
    $max_tokens = min($word_limit * 4, 2048);

    // Updated API endpoint for GPT-3.5 Turbo
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => json_encode([
            'model'       => 'gpt-3.5-turbo',
            'messages'    => [
                ['role' => 'system', 'content' => 'You are a helpful assistant.'],
                ['role' => 'user', 'content' => $prompt],
            ],
            'max_tokens'  => $max_tokens,
            'temperature' => 0.7,
            'top_p'       => 1.0,
        ]),
    ]);

    if (is_wp_error($response)) {
        wp_send_json_error(['message' => 'API request failed: ' . $response->get_error_message()]);
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    // Check if the response has the correct structure
    if (isset($body['choices'][0]['message']['content'])) {
        wp_send_json_success(['text' => trim($body['choices'][0]['message']['content'])]);
    } else {
        wp_send_json_error(['message' => 'Error from OpenAI: ' . json_encode($body)]);
    }
}
add_action('wp_ajax_openai_generate_text', 'openai_generate_text');
add_action('wp_ajax_nopriv_openai_generate_text', 'openai_generate_text');
?>

Math.random() based function not working inside another function

I was coding a simulator for a TTRPG with ‘exploding dice’, which basically means if you roll a max number on one die then you reroll that die and add the result to the max number. That means it is 100% impossible to get a result equal to a multiple of the max number.

I coded one function, explosiveDiceRoll(), that rolls a single exploding dice and it works, it can never equal eight (tested with a never-ending while loop).

However, I coded another function, multiDiceRoll(), that can roll multiple exploding dice and roll with advantage/disadvantage. In multiDiceRoll(), I use explosiveDiceRoll() and run it through loops and whatnot to simulate multiple dice and advantages.

The problem is that explosiveDiceRoll() is somehow rolling the max number while inside the multiDiceRoll() function, which should be impossible. Why is this? Also, how do I solve it?

I encountered a similar problem where Math.random() was the same each time when put into a loop. I don’t know if that’s related or helpful.

I ran both functions at separate times in a while loop. While loop general design:

while([function with an input of a 1d8, no advantage] != 8) {}

The idea is that since getting an 8 is simply impossible, the while loop will continue forever. I waited at most a few seconds each time.

explosiveDiceRoll() continued forever, it works properly

multiDiceRoll() quickly stopped, always ending with this in the console:

1d8
8,8 - SHOULD NOT EQUAL
[ 8 ]
function explosiveDiceRoll(maxRoll) {
    let roll = diceRoll(maxRoll);
    let sum = roll;
    while(roll === maxRoll) { //Reroll and add everytime it hits the max roll
        roll = diceRoll(maxRoll);
        if(roll === 0) console.log("zero error");
        sum += roll
    }
    return sum;
    
}
function multiDiceRoll(diceString,advantage) {
    diceString = diceString.split("d");
    // console.log(diceString);
    let diceAmount= diceString[0];
    let diceType = diceString[1];
    console.log(`${diceAmount}d${diceType}`);


    let results = [];
    for(let i = 0; i <= Math.min(2,Math.abs(advantage)); i++) {
        let result = 0;
        for(let d = 1; d <= diceAmount; d++) {
            let roll = explosiveDiceRoll(diceType);
            console.log(`${roll},${diceType} - SHOULD NOT EQUAL`);
            result += roll;
        }
        results.push(result);
    }
    console.log(results);
    if(advantage >= 0) return Math.max(...results);
    else return Math.min(...results);
}
export function diceRoll(maxRoll) {
    return Math.ceil(Math.random()*maxRoll);
}

How can I resolve this radio button issue for my custom AWS Cognito user pool attribute during account creation in my authentication flow?

I have 2 custom attributes in my cognito user pool. When the user creates an account it assigns them to one of the groups, and then routes the user to that experience. The code works completely fine when I use a text box input

'custom:userType': {
        label: 'User Type  (You are not required to have a therapist)',
        placeholder: 'Enter "groupA" or "groupB"',
        required: true,
        order: 2,
      },

However,when I change it to the radio button using this code the radio buttons do not show up (see image) How do I fix this???

'custom:userType': {
        label: 'User Type',
        required: true,
        order: 2,
        type: 'radio',
        options: [
          { label: 'groupA', value: 'groupA' },
          { label: 'groupB', value: 'groupB' },
        ],
      },

**Full code example
**

// RootAuth.jsx
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  Authenticator,
  ThemeProvider,
  createTheme,
} from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { fetchAuthSession } from 'aws-amplify/auth';
import { Amplify } from 'aws-amplify';
import awsconfig from './aws-exports';

import { useDataContext } from './context/DataContext';
import { createProfileAfterSignUp } from './utils/createUserProfile';

// Configure Amplify
Amplify.configure(awsconfig);

const myCustomTheme = createTheme({
  name: 'MyCustomTheme',
  tokens: {
    colors: {
      brand: {
        primary: {
          100: '#5a3eaf', // your main purple
        },
      },
    },
    components: {
      tabs: {
        item: {
          _active: {
            color: '#5a3eaf',
            borderColor: '#5a3eaf',
          },
        },
      },
      button: {
        link: {
          color: '#5a3eaf',
        },
        primary: {
          backgroundColor: '#5a3eaf',
        },
      },
    },
  },
});

export default function RootAuth() {
  const formFields = {
    signUp: {
      email: { label: 'Email', type: 'email', required: true, order: 1 },

      given_name: {
        label: 'First Name',
        placeholder: 'Enter your first name',
        required: true,
        order: 3,
      },
      family_name: {
        label: 'Last Name',
        placeholder: 'Enter your last name',
        required: true,
        order: 4,
      },
      password: {
        label: 'Password',
        placeholder: 'Enter your password',
        required: true,
        order: 5,
      },
      confirm_password: {
        label: 'Confirm Password',
        placeholder: 'Confirm your password',
        required: true,
        order: 6,
      },
      'custom:userType': {
        label: 'User Type',
        required: true,
        order: 2,
        type: 'radio',
        options: [
          { label: 'groupA', value: 'groupA' },
          { label: 'groupB', value: 'groupB' },
        ],
      },
    },
  };

  return (
    <ThemeProvider theme={myCustomTheme}>
      {/* 
         This div is absolutely positioned to fill the viewport (no extra scroll)
         and is centered so that the Authenticator sits in the middle on mobile. 
       */}
      <div
        style={{
          position: 'fixed',
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
          margin: 0,
          padding: 0,
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          // Choose overflow: 'hidden' if you never need scrolling;
          // or 'auto' if your form might grow taller than the screen.
          overflow: 'hidden',
          backgroundColor: '#fff', // so we don't see underlying page
          zIndex: 9999, // ensure it sits above any nav
        }}
      >
        <Authenticator formFields={formFields}>
          {({ user }) => {
            if (!user) return null;
            return <PostSignInRedirect />;
          }}
        </Authenticator>
      </div>
    </ThemeProvider>
  );
}

function PostSignInRedirect() {
  const navigate = useNavigate();
  const { setUserAndFetchData } = useDataContext();

  useEffect(() => {
    async function handleSignIn() {
      try {
        console.log('Fetching auth session...');
        const session = await fetchAuthSession();
        const payload = session.tokens.idToken.payload;
        console.log('ID token payload:', payload);

        // Attempt to create user profile if it doesn't exist
        try {
          console.log('Attempting to create user profile...');
          await createProfileAfterSignUp(payload);
        } catch (error) {
          console.warn(
            'User profile creation error (may already exist):',
            error
          );
        }

        // Set user in context & conditionally fetch data
        setUserAndFetchData(payload);

        // Route based on user type
        const userTypeValFromCustom =
          payload['custom:userType']?.toLowerCase() || '';
        let userTypeVal = userTypeValFromCustom;

        if (!userTypeVal) {
          const groups = payload['cognito:groups'] || [];
          if (groups.includes('groupA')) {
            userTypeVal = 'groupA';
          } else if (groups.includes('groupB')) {
            userTypeVal = 'groupB';
          }
        }

        if (userTypeVal === 'groupA') {
          navigate('/groupA');
        } else {
          navigate('/groupB');
        }
      } catch (error) {
        console.error('Error during post-sign-in:', error);
        navigate('/groupB');
      }
    }
    handleSignIn();
  }, [navigate, setUserAndFetchData]);

  return null;
}

How to call one javascript class method from another class

I have a Javascript class which each instance is attached to a DOM object. That class contains a method to hide the DOM object. For example

index.html

<div id="myDomObject">Content</div>

main.js

class MyClass {
    constructor(element)
    {
        this.component = element;
    }

    hideElement()
    {
        this.component.classList.add('hidden');
    }
}

let myDiv = new MyClass(document.getElementById("myDomObject");

I have a series of other classes which will also hide the same DOM object after something happens, such as the click of a button or result of an ajax request.

main2.js

class MySecondClass {
    constructor(element)
    {
        this.component = element;
        this.addClickEvent(some_button);
    }

    addClickEvent(element)
    {
        element.addEventListener('click', function()
        {
            document.getElementById("myDomObject").classList.add('hidden');
        }
    }
}

It doesn’t feel right to re-create the same method over and over again in each of my classes, just to replicate the method of the original class. That is not very DRY. What I would like to do is call the ‘hide’ method of this original class

I have tried this as below

class MySecondClass {
    constructor(element)
    {
        this.component = element;
        this.addClickEvent(some_button);
        this.domObject = new MyClass(document.getElementById("myDomObject");
    }

    addClickEvent(element)
    {
        element.addEventListener('click', function()
        {
            this.domObject.hide();
        }
    }
}

This obviously instantiates multiple instances of the main class. If I have ten other classes, I will be calling those ten classes and then ten instances of the main class. Event listeners are added to the DOM object ten times etc.

How can I achieve this without overloading class instantiations?

Puppeteer Scraping: See XHR response data before request completes for real time data

I am using puppeteer to scrape a website for real time data in nodejs. Instead of scraping the page, I am watching the backend requests and capturing the JSON/TEXT responses so I have more structured data. (and seeing more data than what’s being displayed in the browser) Everything is working except some of the data is updated with a Firestore request. I can capture the response to that request, but only get the data when the request is complete.

If I monitor the request/response in the browser, I can see that there are several numbered “messages” in the response packet:

16
[[732,[“noop”]]]
16
[[733,[“noop”]]]
123
[[734,[{
“targetChange”: {
“resumeToken”: “CgkIgqb+n7TFiwM=”,
“readTime”: “2025-02-15T09:53:39.558146Z”
}
}
]]]
16
[[735,[“noop”]]]

with each message coming in over several seconds. At some point the request completes and a new firebase request is issued. The problem is I only see all these messages in my app once the response is complete and not in real time as each message comes in. (which my app requires in order to display changes in real time)

Is there a way to see the response data as it is received and not just when the request is completed?

    page.on('response', async (response) => {    
        if (response.request().resourceType() === 'xhr') {
            console.log('Firestore Response URL:', response.url());
            const theResponse = await response.text();
            console.log('response.text: ', theResponse);
        }
    }

Issues with Date Function on IOS

So I’m currently building a Website for my retro moped club and I’ve encountered an issue I am unable to solve. I am writing everything in plain JS (don’t hate me, I just like writing everything myself), HTML and CSS. For the members page, I use a JSON file which has all the members Data saved, including their birthdate. I’m using this JS code to calculate their age

// Fake data for Test
const member = {
  "Name": "John Doe",
  "Birthdate": "1990-07-15T00:00:00Z",
};

// Function to calculate age
const ageP = document.createElement("p");
const now = new Date();
const birthdate = new Date(member.Birthdate);

let age = now.getFullYear() - birthdate.getFullYear();

if (
  now.getMonth() < birthdate.getMonth() ||
  (now.getMonth() === birthdate.getMonth() && now.getDate() < birthdate.getDate())
) {
  age--;
}

console.log(`Birthdate: ${member.Birthdate}`);
console.log(`Age: ${age}`);

But for some reason, the calculation does not work on IOS Devices, no matter which browser I use.

Next.js UI Not Updating After Login Until Manual Refresh

I’m working on a Next.js 14 app with a login system that uses server actions and cookies for authentication. However, when I log in:

The UI does not update immediately after login.
Elements appear stuck together and unstyled until I manually refresh the page.
Once I refresh, everything looks normal.

Before Refreshing

After Refreshing

Here’s the code:

// API CODE 

import { executeQuery } from "@/app/lib/db";
import { NextRequest, NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import * as jose from "jose";

export async function POST(request: NextRequest) {
  try {
    const { Email, Password } = await request.json();

    const user = await executeQuery("SELECT * FROM user WHERE Email = ?", [Email]);

    if (!user || user.length === 0) {
      return NextResponse.json({ error: "Wrong email" }, { status: 400 });
    }

    const isCorrectPassword = await bcrypt.compare(Password, user[0].Password);

    if (!isCorrectPassword) {
      return NextResponse.json({ error: "Wrong password!" }, { status: 400 });
    }

    const secret = new TextEncoder().encode(process.env.JWT_SECRET);
    const alg = "HS256";

    const jwt = await new jose.SignJWT({ UserID: user[0].UserID, Email: user[0].Email })
      .setProtectedHeader({ alg })
      .setExpirationTime("2h")
      .setSubject(user[0].UserID.toString())
      .sign(secret);

    return NextResponse.json({ token: jwt });
  } catch (error) {
    return NextResponse.json({ message: error || "Server error" }, { status: 500 });
  }
}
// Log-in Action

"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export default async function logInAction(
  currentState: any,
  formData: FormData
): Promise<string> {
  const email = formData.get("email");
  const password = formData.get("password");

  const response = await fetch(new URL("/api/login", "http://localhost:3000"), {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ Email: email, Password: password }),
  });

  const json = await response.json();

  cookies().set("Authorization", json.token, {
    secure: true,
    httpOnly: true,
    expires: Date.now() + 24 * 60 * 60 * 1000 * 3, // 3 days
    path: "/",
    sameSite: "strict",
  });

  if (response.ok) {
    redirect("/homepage");
  } else {
    return json.error;
  }
}
// Login Page

"use client";
import React from "react";
import "@/app/styles/login-style.css";
import Image from "next/image";
import loginLogo from "@/public/haytek-login-logo.png";
import { Button, Form } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import { useFormState } from "react-dom";
import logInAction from "./loginAction";
import Link from "next/link";

const LogInPage = () => {
  const [error, formAction] = useFormState(logInAction, undefined);
  return (
    <main>
      <Image src={loginLogo} alt="Logo" style={{ pointerEvents: "none" }} />
      <div className="log-in-container">
        <Form className="login-form" action={formAction}>
          <Form.Group className="form-group">
            <Form.Label>Email:</Form.Label>
            <Form.Control
              name="email"
              type="email"
              placeholder="Enter your email"
              required
            />
          </Form.Group>
          <Form.Group className="form-group">
            <Form.Label>Password:</Form.Label>
            <Form.Control
              name="password"
              type="password"
              placeholder="Enter your password"
              required
            />
          </Form.Group>
          <Button variant="primary" type="submit" className="submit-button">
            Log In
          </Button>
          {error && <p className="error-message">{error}</p>}
          <Link href={"/signup"} className="form-link">
            Don't have an account? Sign up
          </Link>
        </Form>
      </div>
    </main>
  );
};

export default LogInPage;

Created npm Package but not able to load :: Module not found:

I have created the react component and trying to create the package of it and publish it.
I was testing it with npm link as well as npm install but after compiling it always gives an error :: Module not found. Even though it is present in the node modules.

package.json

{
  "name": "field-input",
  "version": "0.0.1",
  "description": "Input Field Component",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsup",
    "lint": "tsc"
  },
  "keywords": [
    "react",
    "component",
    "input",
    "field"
  ],
  "author": "fastkit",
  "license": "ISC",
  "peerDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "dependencies": {
    "lodash": "^4.17.21",
    "lucide-react": "^0.475.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.0.6",
    "@tsconfig/recommended": "^1.0.8",
    "@types/lodash.debounce": "^4.0.9",
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    "autoprefixer": "^10.4.20",
    "esbuild": "^0.25.0",
    "esbuild-css-modules-plugin": "^3.1.4",
    "postcss": "^8.5.2",
    "tailwindcss": "^4.0.6",
    "tsup": "^8.3.6",
    "typescript": "^5.7.3"
  }
}

tsconfig.ts

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "strictNullChecks": true,
    "target": "ESNext",
    "moduleResolution": "node",
    "module": "ESNext",
    "declaration": true,
    "jsx": "react-jsx",
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

tsup.config.ts

import { defineConfig } from "tsup";
export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
  minify: true,
  dts: true,
  outDir: "dist",
  sourcemap: true,
  clean: true,
});

/src/index.ts

import "./index.css";
import FieldInput from "./FieldInput";
export default FieldInput;

/src/Index.tsx

React Component

USAGE

"use client"
import FieldInput from "field-input";
export default function Home() {
  return (
    <div className="">
      <FieldInput label="test"/>
    </div>
  );
}

How to make the field-sizing property regain control after a resize

Width the new field-sizing CSS property, you can allow certain form related elements to grow/shrink in size as content is inputted. (Only supported on Chrome as of writing this: caniuse)

If you input some text into the textarea in the snippet below, you can see the feature working. However, if you use the resize feature of the textarea at all, you will notice the field-sizing feature completely stops working. It is as if the browser just removes the property from the element after it has been resized.

My question is: how can we allow the field-sizing property to continue to work after a resize has occurred?

I have tried programatically setting resize: none; and field-sizing: content; after the resizing but nothing seems to allow the field-sizing property to regain control.

textarea {
  field-sizing: content;
  min-width: 6rem;
  max-width: 16rem;
}
<textarea></textarea>

How do you get the datetimeoffset in c# that comes back from sqlserver as dattimeoffset and return this value in javascript date to get local datetime

I have this object:

        public partial class TblThread
        {
            public int Id { get; set; }

            public string? Guid { get; set; }

            public int? FrameworkId { get; set; }

            public string? Title { get; set; }

            public string? Question { get; set; }

            public string? Tags { get; set; }

            public DateTimeOffset? DateCreated { get; set; }

            public int? UserId { get; set; }

            public virtual TblUser? User { get; set; }
        }

The DateCreated field in the database has the datatype as DateTimeOffset(7) and default value is (getutcdate())

But when i do this and look at the value:

@thread.DateCreated 

this value returns this:

"15/02/2025 07:07:37 &#x2B;00:00"

But the value in the database is:

2025-02-15 07:07:37.2366667 +00:00

So how do i return the datetimeoffset from c# and get the localtime from the utc timestamp in new Date() in javascript?

if array returns gq_online 1 display online badge

im currently making a game server page, i have a badge next to each game server which displays online how could i make it work real time, so it displays
when online display

 <td><label class="badge badge-success">Online</label></td>
             when offline display 
<td><label class="badge badge-danger">Offline</label></td>

the query returns back gq_online 1 if the value is 0 how can i make this set the badge 1 online for online badge 0 for offline badge?

<tbody>
                <tr scope="row">
                </tr>
    <tr>
    <?php $counter = 1; ?>
      <?php foreach ($results as $server) : ?>
          <th scope="row"><?php echo $counter++; ?></th>
          <td><?php echo $server['gq_address'] ?? 'N/A'; ?></td>
          <td><?php echo $server['gq_port_client'] ?? 'N/A'; ?></td>
          <td><?php echo $server['gq_hostname'] ?? 'N/A'; ?></td>
          <td><?php echo $server['gq_mapname'] ?? 'N/A'; ?></td>
          <td><?php echo $server['gq_name'] ?? 'N/A'; ?></td>
          <td><?php echo $server['gq_numplayers'] ?? 0; ?></td>   
         <td><label class="badge badge-success">Online</label></td>
        </tr>
      <?php endforeach; ?>
              </tbody>