Laravel 10 show modal

im trying to show modal by sending the data from controller to view, but the modal is not shown, I don’t know what wrong, can anyone help me?

here is the code:

controller submit:

if ($success) {
                             return view('pages.approval.modal_ap', [
                                'post_ap' => $postap,
                            ]);
                            }

index view:

$('#submit_app').on('click', function(event){
                event.preventDefault();
                        $.ajax({
                            url: "{{ route('submit_approval') }}",
                            type: 'POST',
                            data: formData,
                            processData: false,
                            contentType: false, 
                            success: function(data_submit){
                                console.log(data_submit);
                                if (data_submit== "S") {
                                    console.log("masuk modal");
                                    $('#post_ap').modal('show');

                                    $('#post_ap .btn-primary').on('click', function () {
                                        $('#post_ap').modal('hide');
                                        setTimeout(function () {
                                            location.reload();
                                        }, 1000);
                                    });
                                }

modal view:

        <div class="modal fade" tabindex="-1" id="post_ap">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="modal-title">Success</h3>
                </div>
                <div class="modal-body">
                    <div class="row">
                        <div class="col-sm-6 col-md-6 col-lg-6">
                                <div class="row mb-3">
                                    <div class="col-lg-4">Document Number</div>
                                    <div class="col-lg-8"><b>{{ $post_ap['tes'][0]->a }}</b></div> <!-- Akses dengan -> -->
                                </div>

                                <div class="row">
                                    <div class="col-lg-4">Document Date</div>
                                    <div class="col-lg-8"><b>{{ date('d.m.Y', strtotime($post_ap['tes'][0]->b)) }}</b></div> <!-- Akses dengan -> -->
                                </div>

                                <div class="row mb-3">
                                    <div class="col-lg-4">Reference</div>
                                    <div class="col-lg-8"><b>{{ $post_ap['tes'][0]->c }}</b></div> <!-- Akses dengan -> -->
                                </div>

                                <div class="row mb-3">
                                    <div class="col-lg-4">Currency</div>
                                    <div class="col-lg-8"><b>{{ $post_ap['tes'][0]->d }}</b></div> <!-- Akses dengan -> -->
                                </div>

                                <div class="col-sm-6 col-md-6 col-lg-6">
                                    <div class="row mb-3">
                                        <div class="col-lg-4">Company Code</div>
                                        <div class="col-lg-8"><b>{{ $post_ap['tes'][0]->e }}</b></div> <!-- Akses dengan -> -->
                                    </div>

                                    <div class="row mb-3">
                                        <div class="col-lg-4">Posting Date</div>
                                        <div class="col-lg-8"><b>{{ date('d.m.Y', strtotime($post_ap['tes'][0]->f)) }}</b></div> <!-- Akses dengan -> -->
                                    </div>
                                </div>

                                <div class="col-lg-4">
                                    <div class="row">
                                        <div class="col-lg-4">Fiscal Year</div>
                                        <div class="col-lg-8"><b>{{ $post_ap['tes'][0]->g }}</b></div> <!-- Akses dengan -> -->
                                    </div>
                                </div>

                        </div>
                        <br>
                        <div class="row">
                            <div class="col-lg-12 table-responsive">
                                <table class="table table-bordered">
                                    <thead>
                                        <tr>
                                            <th>No</th>
                                            <th>CoCd</th>
                                            <th>PK</th>
                                            <th>TX</th>
                                            <th>Account</th>
                                            <th>Description</th>
                                            <th>Amount</th>
                                            <th>Curr</th>
                                            <th>Profit Ctr</th>
                                            <th>Segment</th>
                                            <th>Text</th>
                                            <th>Assignment</th>
                                            <th>Cost Ctr</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                      @foreach($post_ap['a'] as $index => $t)
                                      @php
                                        $acc = '';
                                        $desc = ''; 

                                        if (trim($t->LIFNR) == '') {
                                            $acc = trim($t->z);
                                            foreach ($post_ap['t_skat'] as $skat) {
                                                if (trim($skat->s) == 'E' && trim($skat->h) == 'DPCA' && trim($skat->f) == $acc) {
                                                    $desc = trim($skat->u); 
                                                    break; 
                                                }
                                            }
                                        } else {
                                            $acc = trim($t->l); 
                                            foreach ($post_ap['t_lfa1'] as $lfa1) {
                                                if (trim($lfa1->q) == $acc) {
                                                    $desc = trim($lfa1->g); 
                                                    break; 
                                                }
                                            }
                                        }
                                        @endphp

                                        <tr>
                                            <td>{{ ltrim($t->b, '0') }}</td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ trim($acc, '0') }}</td>
                                            <td>{{ $desc }}</td>
                                            <td>
                                                @if (trim($t->b) == 'H')
                                                    -
                                                @else
                                                    @if ($t->b == 'DNPA')
                                                        {{ number_format($t->b, 2, '.', ',') }}
                                                    @else
                                                        {{ number_format($t->b * 100, 0, '.', ',') }}
                                                    @endif
                                                @endif
                                            </td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ ltrim($t->b, '0') }}</td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ $t->b }}</td>
                                            <td>{{ ltrim($t->b, '0') }}</td>
                                        </tr>
                                        @endforeach
                                    </tbody>
                                </table>
                            </div>
                        </div>
                </div>
                {{-- @endif --}}

                <div class="modal-footer">
                    <button type="button" class="btn btn-primary">OK</button>
                </div>
            </div>
        </div>
    </div>

the console.log(“masuk modal”) is call but the modal is not show

console log

I already try to call modal with just empty html and it works
enter image description here

and I also try to console log the modal

 console.log("masuk modal");
                                    console.log($('#post_ap').modal('show'));

and it send this

enter image description here

can anyone help me, I don’t know whats wrong

Typescript string index in foreach loop

I am building a tool to check statuses on the network. The environment file has a bunch of addresses to various items, like VMs, DBs, etc. The type of the env looks like this:

export type EnvDefinition = {
  'DetailedErrors': boolean,
  'Logging': LogDefinition,
  'SystemElementGroups': {
    'VMs': SystemElement[],
    'Applications': SystemElement[],
    'Databases': SystemElement[],
  },
}

SytemElement is just another type that defines the IP address and whatever else is needed.

When I load the env data from the json file, I cast it like this:

import envData from '../development.json';
const data: EnvDefinition = envData;

Then I try to access the individual group data like this:

const assetTypes = ['VMs', 'Applications', 'Databases'];
for (const assetType of assetTypes) {
    const connectStrings = data.SystemElementGroups[assetType];

No matter what I try I keep getting a TS error: TS7053: Element implicitly has an any type because expression of type string can't be used to index type

How can I define this so I can access the SystemElementGroups in a loop?

Thanks

The token is not installed in the headers

The thing is that I’m trying to get a token from a cookie using the ready-made CookieService library and output it to console.log() – and the most interesting thing is that it is displayed in console.log() and even written to the headers, but the client still gives me an error side – No token provided , I really don’t understand the options for solving this problem anymore, apparently there is a problem in my code that I don’t see

Here is my Interceptor where I am trying to set the token in the headers

I will provide the most basic code that is needed

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpRequest, HttpHeaders, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { TokenService } from '../services/token/token.service';
import { CookieService } from 'ngx-cookie-service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private tokenService: TokenService, private cookieService: CookieService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const accessToken = this.cookieService.get('accessToken');
    
    if (accessToken) {
      req = req.clone({
        setHeaders: {
          Authorization: `Bearer ${accessToken}`
        }
      });
    } else {
      console.error('Access token not found in cookie');
    }

    return next.handle(req);
  }
}

--------------------------------------------------------------------
const login = async (req, res) => {
    try {
        const { email, password } = req.body;
        const user = await User.findOne({ email });

        if (!user) {
            return res.status(401).json({ message: 'Authentication failed' });
        }

        const isPasswordValid = await bcrypt.compare(password, user.password);

        if (!isPasswordValid) {
            return res.status(401).json({ message: 'Authentication failed' });
        }

        const { accessToken, refreshToken } = generateTokens(user);

        res.cookie('accessToken', accessToken, {
            maxAge: 30000
        });

        res.cookie('refreshToken', refreshToken, {
            maxAge: 7 * 24 * 60 * 60 * 1000,
        });

        res.status(200).json({ accessToken, refreshToken });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
};
--------------------------------------------------------------------
const getUserById = async (req, res) => {
    const userId = req.id;

    let user;
    try {
        user = await User.findById(userId, "-password");
        console.log('User', user);
    } catch(err) {
        return new Error(err);
    }
    if(!user) {
        return res.status(404).json({message: "User not found"});
    }
    return res.status(200).json({user});
};
--------------------------------------------------------------------
const jwt = require('jsonwebtoken');

const verifyToken = (req, res, next) => {
  const authorizationHeader = req.headers.authorization;

  if (!authorizationHeader) {
    return res.status(401).json({ message: "No token provided" });
  }
  console.log('AuthHeader', authorizationHeader);

  const token = authorizationHeader.split(' ')[1];
  if (!token) {
    return res.status(401).json({ message: "No token found" });
  }
  console.log('Tokenssss', token);

  jwt.verify(token, process.env.SECRET_KEY, (err, decoded) => {
    if (err) {
      console.error('Error verifying token:', err);
      return res.status(400).json({ message: "Invalid token" });
    }

    req.id = decoded.userId;
    next();
  });
};

module.exports = verifyToken;


**And routes **
router.post('/login', userAuthController.login);
router.get('/user', verifyToken, userAuthController.getUserById);

Loading a 31mb javascript file in react native is giving error, any solutions?

I am building an app in react native without backend so i am loading a javascript data file by exporting the data like this :

export const dataArray = [
{
"field1": 1,
"field2": "text"
},
{
"field1": 2,
"field2": "text"
},
];

So the file i am loading is 31mb so when i start my app on expo go i get this error in the console

error: Appdatabasequotes.js: A jest worker process (pid=13648) was terminated by another process: signal=SIGTERM, exitCode=null. Operating system logs may contain more information on why this occurred.

i need your help please, what is the solution for such an issue, is there any other way to call the file in the apps screens ?

Is the includes function for strings in javascript impacted by punctuation?

I am trying to figure out a bug where I am looking for a subset of text in an array of iconNames. My filter looks like this:

name = isName ? images[iconNames.filter(icon => icon.includes(string))[0]] : ""

this works for all the other strings like “Surfing” or “Sports”, and have icon names SurfingIcon or SportsIcon. My issue is with the string “Who has time for hobbies?” which has an icon name WhoHasTimeForHobbiesIcon. I know includes is supposed to grab subset strings and I feel like this qualifies. I am wondering if the ? is impacting this or if there is something else going on.

I could probably change the way this is filtered, but i just wound up manually setting it for the one string that doesn’t work.

ReferenceError: ‘x’ is not defined in jest

I am trying to write a unit test for my angular service. The service makes use of a static method from inside a model class.
I used jest to mock the class like that:

jest.mock('modelName', () => ({
  ...jest.requireActual('modelName'), // This will preserve other exports
  RunTreeNodeModel: {
    fromDTO: jest.fn(),
  }
}));

However, when I debug the test, it turns out that jest cannot find it and says ReferenceError: ‘RunTreeNodeModel’ is not defined in jest.

I made sure to import it RunTreeNodeModel the test class. I have another test class in the same package that makes use of RunTreeNodeModel, and it runs just fine. It is imported and mocked in the same way, so I don’t understand what the issue could be.
Anyone has any idea of where to look?

How to avoid redirect in Vue 3 composition API using ajax

I’m trying to let the data in a submission form be sent to my email and also added to a google sheet. I found one way of doing it from this link github link. It actually worked for both the purposes I used it for.

My problem is after submitting the form, the page redirects to a json page but I don’t want that to happen. I’ve tried the method they added in the repo but it didn’t work. They mentioned Ajax in the repo but I can’t figure out how to make it work
this is a vue play ground to what I want to do Vue Playground

Extracting data points from Tableau visualization on a web page

Consider having various widgets on a screen and want to extract specific data attributes from those widgets for use outside of that widget or Tableau visualization. For example, an aggregate count from each widget and show a Javascript alert.

If within the report we already have those aggregate values defined, when making the JS Tableau visualization call, can we specify output values, in addition to the visualization for ETL outside of the Tableau visualization widget? If it is possible, can we output complex objects or only single-value attributes?

enter image description here

Doing this with some default Tableau visualization functionality would be optimal. However, one consideration is to make a second call to the visualization’s original data source to extract the required raw data.

Open to recommendations.

Merging two canvas file

Axis helper code :

    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const AXIS_LENGTH = 50; // Length of the axis lines

    function drawAxis(mouseX, mouseY) {
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // Draw vertical axis line
      ctx.beginPath();
      ctx.moveTo(mouseX, mouseY - AXIS_LENGTH / 2);
      ctx.lineTo(mouseX, mouseY + AXIS_LENGTH / 2);
      ctx.stroke();

      // Draw horizontal axis line
      ctx.beginPath();
      ctx.moveTo(mouseX - AXIS_LENGTH / 2, mouseY);
      ctx.lineTo(mouseX + AXIS_LENGTH / 2, mouseY);
      ctx.stroke();
    }

    document.addEventListener("mousemove", function (event) {
      const mouseX = event.clientX;
      const mouseY = event.clientY;

      const invertedX = window.innerWidth - mouseX;
      const invertedY = window.innerHeight - mouseY;

      drawAxis(invertedX, invertedY);
    }); 

and

main.js :

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let isDrawing = false;
let lastX = 0;
let lastY = 0;

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

function startDrawing(e) {
    isDrawing = true;
    [lastX, lastY] = [canvas.width - (e.pageX - canvas.offsetLeft), canvas.height - (e.pageY - canvas.offsetTop)];
}

function draw(e) {
    if (!isDrawing) return;

    const x = canvas.width - (e.pageX - canvas.offsetLeft);
    const y = canvas.height - (e.pageY - canvas.offsetTop);

    ctx.strokeStyle = 'black';
    ctx.lineWidth = 5;
    ctx.lineCap = 'round';

    ctx.beginPath();
    ctx.moveTo(lastX, lastY);
    ctx.lineTo(x, y);
    ctx.stroke();

    [lastX, lastY] = [x, y];
}

function stopDrawing() {
    isDrawing = false;
}

canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);

canvas.addEventListener('touchstart', (e) => {
    e.preventDefault();
    startDrawing(e.touches[0]);
});

canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    draw(e.touches[0]);
});

canvas.addEventListener('touchend', stopDrawing);

function clearCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}


// Listen for window resize event
window.addEventListener('resize', resizeCanvas);


I want the axis helper to be in the place of inverted mouse coordinates. so when i draw in inverted stroke the axis will work as a guide for where will the stroke appear

I tried directly type the function in my main js file but it did not work. I could see the axis but when I try to draw it becomes wider and the stroke does not appear. OR is there is any way to use 2 canvas elements at the same time with also the index of the elements to be same.

What is the best React framework for client side rendering? [closed]

I am starting a new React project that is required to render in the client, not the server. React’s website no longer shows how to create a client React app and only recommends frameworks that render on the server. So if I don’t need server side rendering, what React framework should I use?

This project requires WebGL which is only in the client.

Can’t push custom dimension metric to Google Analytics 4 (GA4) from React

I created a custom dimension in GA4 called company_name and it’s scoped at the user level. ga4 custom dimension dashboard

On the React side, I’m trying to add the company name as an event parameter as part of my custom event tracking like this:

import ReactGA from 'react-ga4';

ReactGA.initialize([
  {
    trackingId: process.env.REACT_APP_GA4_DESTINATION_ID!,
  },
  {
    trackingId: process.env.REACT_APP_GA4_ANALYTICS_ID!,
  },
]);

export const analytics = () => {
  return {
    trackEvent: ({
      eventCategory,
      eventAction,
      eventLabel,
      eventValue,
      nonInteraction,
      companyName,
    }: ITrackEventParams) => {
      ReactGA.event(
        {
          category: eventCategory,
          action: eventAction,
          label: eventLabel, // optional
          value: eventValue, // optional, must be a number
          nonInteraction: nonInteraction, // optional, true/false
        },
        { company_name: companyName }
      );
    },
  };
};

But when I trigger the event I don’t see the company_name in the event parameters when I check the Google Analytics Debugger extension.

GA debugger

I’ve seen posts similar to my issue but they’re all for pushing custom dimensions to universal analytics (UA), not GA4 which is what I’m using now. I’m also using the react-ga4, not the react-ga library.

I’ve also tried doing

ReactGA.event(
        {
          category: eventCategory,
          action: eventAction,
          label: eventLabel,
          value: eventValue,
          nonInteraction: nonInteraction
        },
        { dimension2: companyName }
);

And
ReactGA.set({dimension2: companyName}) or ReactGA.set({company_name: companyName})

And

ReactGA.gtag("set", "company_name", {
    company_name: companyName
});

Socket.IO: Is it possible to emit and listen for events in the same client

Like the title says, I wanna know if I can establish a socket.io connection in my frontend and then make the same instance emit messages. Then listen for those same messages within the frontend? I am working on a chat app and want to enable real-time messaging. So according to my component code below, when a user sends a message, the handleSubmit function is called. Inside the handleSubmit function I’m making an AJAX request to upload the text message to the database through the backend server. For receving messages, currently, I only have a useEffect() hook which will execute fetchChats(). But this component currently doesn’t support real-time updates. So my question is how can I setup socket.io in this component. I would want the io connection to emit a message with message data including “recipientEmail” (which is the email id of the user we’re texting to). And then also constantly keep listening for a message, once a message is received it will check whether recipientEmail in the message’s metadata is equal to client (here I’m now talking from the 2nd user’s perspective, where client is the second user.)

My current component is as follows:

import { useEffect, useState } from 'react';
import { Button, ChatBubble, Navbar, Textarea } from 'react-daisyui';
import axios from 'axios';
import io from 'socket.io-client';

const api = `http://localhost:8000`;

function ChatWindow({ recipientNickname, recipientEmail }) {

    const client = window.localStorage.getItem('mail');
    const [chats, setChats] = useState([]);
    const [message, setMessage] = useState('');
    const [counter, setCounter] = useState(0);

    const socket = io() // socket.io connection on the same location as the frontend, i.e. localhost:3010

    async function fetchChats() {
        try {
            const response = await axios.get(`${api}/users/messages?client=${client}&recipient=${recipientEmail}`);
            if (response.data)
                setChats(response.data);
            else
                setChats([]);
        } catch (e) {
            console.log(e);
        }
    }

    useEffect(() => {
        fetchChats();
    }, [recipientEmail]);

    useEffect(() => {
        if (counter > 0) {
            fetchChats();
        }
    }, [counter]);

    const getCurrentTimestamp = () => {
        // ...
    };

    const handleSubmit = async (e) => {
        e.preventDefault();
        if (message) {
            const timestamp = getCurrentTimestamp()

            //posting the message to the database through the backend server
            await axios.post(`${api}/users/messages?client=${client}&recipient=${recipientEmail}&message=${message}&timestamp=${timestamp}`);

            setMessage('');
            setCounter((prevCounter) => prevCounter + 1);
            await fetchChats();
            try {
                // trying to emit a new message
                const res = socket.emit('new-message', { recipientEmail, message, timestamp })
                    console.log(res)
                console.log('Message emitted')
            } catch(e) {
                console.log('Couldnt emit message:n'+e)
            }
        }
    }

    // listening for new messages then comparing recipientEmail with client
    useEffect(() => {
        socket.on('new-message', (data) => {
            console.log('Received new message data:n'+data)
          if (data.recipientEmail === client) {
            setChats((prevChats) => [...prevChats, data]);
          }
        });
        return () => socket.disconnect();
      }, []);

    return (
        <div style={{ marginLeft: 250, marginRight: 20, zIndex: 1 }}>
            <div className='chats' style={{ maxHeight: '75vh', overflowY: 'auto' }}>
                {chats.map((message, index) => (
                    <ChatBubble key={index} end={message.client === client}>
                        <ChatBubble.Message>{message.message}</ChatBubble.Message>
                        {message.client === client ? <ChatBubble.Footer>{message.footer}</ChatBubble.Footer> : <></>}
                    </ChatBubble>
                ))}
            </div>
            <div style={{ position: 'absolute', bottom: 10, left: 250, right: 20 }}>
                <form onSubmit={handleSubmit}>
                    <Textarea
                        rows={1}
                        style={{ width: '100%', borderRadius: 10, margin: 5 }}
                        placeholder='Send a message'
                        className='join-item msg-input'
                        value={message}
                        onChange={(e) => setMessage(e.target.value)}
                        onKeyDown={(e) => {
                            if (e.key === 'Enter' && !e.shiftKey) {
                                e.preventDefault();
                                handleSubmit(e);
                            }
                        }}
                    />
                </form>
            </div>
        </div>
    );
}

export default ChatWindow

I tried implementing a socket.io connection on my own (as in the code above), but it doesn’t work. The message gets emitted and the result of console.log(res) (after emitting the message) gives something like this:

Socket {connected: false, recovered: false, receiveBuffer: Array(0), sendBuffer: Array(1), _queue: Array(0), …}

which clearly states that the connection couldn’t be established.

Multiple checkbox’s with multiple dropdowns and input tags, store selected values together/remove if unchecked

Lets say I have multiple checkbox’s and some text and then a dropdown from 1-10 in the following format

[CHECKBOX1]   [NAME1]    [DROPDOWN1 1-10]

[CHECKBOX2]   [NAME2]    [DROPDOWN2 1-10]

[CHECKBOX3]   [NAME3]    [DROPDOWN3 1-10]

.....

[CHECKBOXN]   [NAMEN]    [DROPDOWNN 1-10]

I know I can capture the checked values into an array like the following

arr.push(document.querySelector('.messageCheckbox').checked); 

and similar I can do it for NAME1,and the :selected option for the values 1-10.

However how do I map each rows values to each other? So if I checkbox1 I will push the typed in Name1, selected dropdown1 ( from 1-10) into one item into an array? Similar say for N checkbox’s. I will hope to get some data in the end like

IF 3 CHECKEDBOXS are CHECKED: {{BOB,2} , {SARAH,5}, {LILY,5}}

Similarly if i uncheck a box, those items would be removed from this array

How to exclude ‘aggFunc’ row from the excel export in Ag-grid

Is there a way to exclude bottom row which displays results of aggFunc a column from the excel export.

Essentially, I want to display average on the screen but want to exclude it when exported as that file will be used to import data elsewhere.

Plunker example:

https://plnkr.co/edit/S6tgvSC91MxdGQcZ

Screenshot of the screen and export xls

  1. I already tried searching various options on the ag-grid documentation

  2. Tried skipFooters and skipPinnedBottom options

Accessing the iPhone camera in iOS Safari on Node-Red Dashboard 2.0

I’m trying to access the camera of iPhone(iOS 17) in safari with a web app I’m building on Node-Red(v3.1.3). What I want to do is to display camera feed on Dashboard 2.0 and be able to take a picture, record video and store them in the server. I am using the code shared in
this question in a template node.

So far I’m able to implement this code in Node-Red and it works on PC Chrome. Chrome asks me to enable the camera for the dashboard and if I allow I can view the camera output. But when I try to use same dashboard on iPhone Safari, I cannot see the camera display. It doesn’t even ask for my permission to use the camera. What am I doing wrong?

Below is the exported Node-Red flow I’ve built so far.
Thanks.

[
    {
        "id": "c911c322aad92eb4",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "cf18f162c725eabc",
        "type": "ui-base",
        "name": "My Dashboard1",
        "path": "/dashboard",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-control"
        ],
        "showPathInSidebar": false,
        "navigationStyle": "default"
    },
    {
        "id": "f33b4cc2097266ae",
        "type": "ui-theme",
        "name": "Default Theme",
        "colors": {
            "surface": "#ffffff",
            "primary": "#0094CE",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px"
        }
    },
    {
        "id": "e2669f7ebdf6c137",
        "type": "ui-page",
        "name": "Page N",
        "ui": "cf18f162c725eabc",
        "path": "/pageN",
        "icon": "home",
        "layout": "grid",
        "theme": "f33b4cc2097266ae",
        "order": -1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "e6e8fb22f71d7392",
        "type": "ui-group",
        "name": "My Group",
        "page": "e2669f7ebdf6c137",
        "width": 6,
        "height": 1,
        "order": -1,
        "showTitle": true,
        "className": "",
        "visible": true,
        "disabled": false
    },
    {
        "id": "3afec64dc18d92b8",
        "type": "ui-group",
        "name": "Group Name",
        "page": "e2669f7ebdf6c137",
        "width": "6",
        "height": "1",
        "order": -1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "167a8a7d56b7f4e7",
        "type": "ui-template",
        "z": "c911c322aad92eb4",
        "group": "e6e8fb22f71d7392",
        "page": "",
        "ui": "",
        "name": "iOS Camera Access",
        "order": 0,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>nn<!DOCTYPE html>n<html lang="en">nn<head>n    <meta charset="UTF-8">n    <title>Get User Media Code Along!</title>n    <link rel="stylesheet" href="style.css">n</head>nn<body>nn    <div class="photobooth">n        <div class="controls">n            <button onClick="takePhoto()">Take Photo</button>n            <!--       <div class="rgb">n        <label for="rmin">Red Min:</label>n        <input type="range" min=0 max=255 name="rmin">n        <label for="rmax">Red Max:</label>n        <input type="range" min=0 max=255 name="rmax">n        <br>n        <label for="gmin">Green Min:</label>n        <input type="range" min=0 max=255 name="gmin">n        <label for="gmax">Green Max:</label>n        <input type="range" min=0 max=255 name="gmax">n        <br>n        <label for="bmin">Blue Min:</label>n        <input type="range" min=0 max=255 name="bmin">n        <label for="bmax">Blue Max:</label>n        <input type="range" min=0 max=255 name="bmax">n      </div> -->n        </div>nn        <canvas class="photo"></canvas>n        <video class="player"></video>n        <div class="strip"></div>n    </div>nn    <audio class="snap" src="./snap.mp3" hidden></audio>nn    <script src="scripts.js"></script>nn</body>nn</html>nn</template>nn<script>n    export default {n        data() {n            // define variables available component-widen            // (in <template> and component functions)n            n        },n        watch: {n            // watch for any changes of "count"n            n        },n        computed: {n            // automatically compute this variablen            // whenever VueJS deems appropriaten            n        },n        methods: {n            // expose a method to our <template> and Vue Applicationn            increase: function () {n                this.count++n            },n            takePhoto: function () {n            // played the soundn            snap.currentTime = 0;n            snap.play();n            n            // take the data out of the canvasn            const data = canvas.toDataURL('image/jpeg');n            const link = document.createElement('a');n            link.href = data;n            link.setAttribute('download', 'handsome');n            link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;n                            strip.insertBefore(link, strip.firstChild);n                        }nn        },n        mounted() {n            // code here when the component is first loadednn            const video = document.querySelector('.player');n            const canvas = document.querySelector('.photo');n            const ctx = canvas.getContext('2d');n            const strip = document.querySelector('.strip');n            const snap = document.querySelector('.snap');nn            // Fix for iOS Safari from https://leemartin.dev/hello-webrtc-on-safari-11-e8bcb5335295n            video.setAttribute('autoplay', '');n            video.setAttribute('muted', '');n            video.setAttribute('playsinline', '')nn            const constraints = {n            audio: false,n                video: {n                    facingMode: 'user'n                }n            }nn            function getVideo() {n                navigator.mediaDevices.getUserMedia(constraints)n                    .then(localMediaStream => {n                    console.log(localMediaStream);n                n            //  DEPRECIATION : n            //       The following has been depreceated by major browsers as of Chrome and Firefox.n            //       video.src = window.URL.createObjectURL(localMediaStream);n            //       Please refer to these:n            //       Deprecated  - https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURLn            //       Newer Syntax - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObjectn                console.dir(video);n                if ('srcObject' in video) {n                    video.srcObject = localMediaStream;n                } else {n                    video.src = URL.createObjectURL(localMediaStream);n                }n                // video.src = window.URL.createObjectURL(localMediaStream);n                video.play();n                })n                .catch(err => {n                console.error(`OH NO!!!!`, err);n                });n            }nn            function paintToCanvas() {n                const width = video.videoWidth;n                const height = video.videoHeight;n                canvas.width = width;n                canvas.height = height;nn                return setInterval(() => {n                    ctx.drawImage(video, 0, 0, width, height);n                    // take the pixels outn                    // let pixels = ctx.getImageData(0, 0, width, height);n                    // mess with themn                    // pixels = redEffect(pixels);nn                    // pixels = rgbSplit(pixels);n                    // ctx.globalAlpha = 0.8;nn                    // pixels = greenScreen(pixels);n                    // put them backn                    // ctx.putImageData(pixels, 0, 0);n                }, 16);n            }nn            nn            function redEffect(pixels) {n                for (let i = 0; i < pixels.data.length; i+=4) {n                    pixels.data[i + 0] = pixels.data[i + 0] + 200; // REDn                    pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREENn                    pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Bluen                }n                return pixels;n            }nn            function rgbSplit(pixels) {n                for (let i = 0; i < pixels.data.length; i+=4) {n                    pixels.data[i - 150] = pixels.data[i + 0]; // REDn                    pixels.data[i + 500] = pixels.data[i + 1]; // GREENn                    pixels.data[i - 550] = pixels.data[i + 2]; // Bluen                }n                return pixels;n            }nn            function greenScreen(pixels) {n                const levels = {};nn                document.querySelectorAll('.rgb input').forEach((input) => {n                    levels[input.name] = input.value;n                });nn                for (i = 0; i < pixels.data.length; i = i + 4) {n                    red = pixels.data[i + 0];n                    green = pixels.data[i + 1];n                    blue = pixels.data[i + 2];n                    alpha = pixels.data[i + 3];nn                    if (red >= levels.rminn                    && green >= levels.gminn                    && blue >= levels.bminn                    && red <= levels.rmaxn                    && green <= levels.gmaxn                    && blue <= levels.bmax) {n                    // take it out!n                    pixels.data[i + 3] = 0;n                    }n                }nn                return pixels;n            }nn            getVideo();nn            video.addEventListener('canplay', paintToCanvas);n        },n        unmounted() {n            // code here when the component is removed from the Dashboardn            // i.e. when the user navigates away from the pagen        }n    }n</script>n<style>n    /* define any styles here - supports raw CSS */n    .my-class {n        color: red;n    }n    html {n    box-sizing: border-box;n    }n    n    *, *:before, *:after {n    box-sizing: inherit;n    }n    n    html {n    font-size: 10px;n    background: #ff0000;n    }n    n    .photobooth {n    background: white;n    max-width: 150rem;n    margin: 2rem auto;n    border-radius: 2px;n    }n    n    /*clearfix*/n    .photobooth:after {n    content: '';n    display: block;n    clear: both;n    }n    n    .photo {n    width: 100%;n    float: left;n    }n    n    .player {n    position: absolute;n    top: 20px;n    right: 20px;n    width:200px;n    }n    n    /*n    Strip!n    */n    n    .strip {n    padding: 2rem;n    }n    n    .strip img {n    width: 100px;n    overflow-x: scroll;n    padding: 0.8rem 0.8rem 2.5rem 0.8rem;n    box-shadow: 0 0 3px rgba(0,0,0,0.2);n    background: white;n    }n    n    .strip a:nth-child(5n+1) img { transform: rotate(10deg); }n    .strip a:nth-child(5n+2) img { transform: rotate(-2deg); }n    .strip a:nth-child(5n+3) img { transform: rotate(8deg); }n    .strip a:nth-child(5n+4) img { transform: rotate(-11deg); }n    .strip a:nth-child(5n+5) img { transform: rotate(12deg); }n</style>",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 740,
        "y": 200,
        "wires": [
            []
        ]
    }
]