After deploying the web using Firebase Hosting, there is an issue with using the OpenAI API through the function

const functions = require('firebase-functions');
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const OpenAI = require('openai');

const app = express();

// CORS 설정
const corsOptions = {
  origin: 'https://nulligpt.web.app', 
  methods: ['GET', 'POST', 'OPTIONS'], 
  allowedHeaders: ['Content-Type'], 
  credentials: true,
};

// CORS 미들웨어 사용
app.use(cors(corsOptions));
app.use(bodyParser.json());

// OpenAI API 키
const OPENAI_API_KEY = 'sk-';

const client = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

// CORS preflight 요청에 대한 핸들링
app.options('*', cors(corsOptions));

// 이름 번역 API 엔드포인트
app.post('/api', async (req, res) => {
  const { name } = req.body;

  if (!name) {
    console.log('Name is missing in the request.');
    return res.status(400).json({ error: 'Name is required' });
  }

  try {
    const response = await client.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: `Translate this name to Korean: ${name}` }],
    });

  
    if (response.choices && response.choices.length > 0) {
      const translatedName = response.choices[0].message.content;
      console.log('Successfully received response from OpenAI:', translatedName);
      return res.json({ translatedName });
    } else {
   
      return res.status(500).json({ error });
    }
  } catch (error) {
    console.log( error);
   
  }
});


exports.api = functions.https.onRequest((req, res) => {
  cors(req, res, async () => {
    if (req.method === 'OPTIONS') {
      
      return res.sendStatus(200);
    } else {
      try {
        await app(req, res);
      } catch (err) {
        console.log( err);
     
      }
    }
  });
});
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="nulli.css" rel="stylesheet" type="text/css"/>
</head>
<body>
    <div class="allcon">
        <div class="toplogo"></div>
        <form id="nameForm" class="form">
            <input class="input" type="text" id="nameInput" placeholder="Enter your name" required>
            <button type="submit">Translate</button>
        </form>
        <div class="word">
            <p class="firstword">Enter your name</p>
            <p class="firstword">you can get korean name</p>
        </div>
        <div id="translated-name" class="output"></div>
    </div>
    <script>
        document.getElementById('nameForm').addEventListener('submit', async function(event) {
            event.preventDefault();
    
            const name = document.getElementById('nameInput').value;
    
            try {
                const response = await fetch('https://us-central1-nulligpt.cloudfunctions.net/api', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json; charset=utf-8',
                    },
                    body: JSON.stringify({ name: name }),
                });
    
                if (response.ok) {
                    const data = await response.json();
                    document.getElementById('translated-name').innerText = `${data.translatedName}`;
                } else {
                    document.getElementById('translated-name').innerText = 'Error: Failed to translate name';
                }
            } catch (error) {
                console.error('Error:', error);
                document.getElementById('translated-name').innerText = 'Error: Failed to fetch the translation';
            }
        });
    </script>
    
</body>
</html>

I created a feature that translates the text I input into Korean.
It worked well during local testing, so I deployed it using
Firebase Hosting and created a function. My Firebase project name is ‘nulligpt’
so the URL is https://nulligpt.web.app/ When sending POST requests from the client,
I use https://us-central1-nulligpt.cloudfunctions.net/api (where ‘api’ is the Firebase function name).
Initially, a CORS error appeared, which seemed correctly resolved, but now, no matter how many times I send a POST request, I receive either a 404 or 500 error.

Below is the network request log.
Request URL:
https://us-central1-nulligpt.cloudfunctions.net/api
Request Method:
OPTIONS
Status Code:
408 Request Timeout
Remote Address:
216.239.36.54:443
Referrer Policy:
strict-origin-when-cross-origin
:authority:
us-central1-nulligpt.cloudfunctions.net
:method:
OPTIONS
:path:
/api
:scheme:
https
accept:
/
accept-encoding:
gzip, deflate, br, zstd
accept-language:
ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
access-control-request-headers:
content-type
access-control-request-method:
POST
origin:
https://nulligpt.web.app
priority:
u=1, i
referer:
https://nulligpt.web.app/
sec-fetch-dest:
empty
sec-fetch-mode:
cors
sec-fetch-site:
cross-site
user-agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36

How to resize and drag an image using vanilla JavaScript

I have a simple Mr Potato Head game with a big Mr Potato Head and a side bar filled with parts. I would like to make the parts, which are images, both draggable and resizable.

The following is the website code, with the dragAndResizeElement function responsible to add event listeners for both the dragging and resizing functionality. I call this function on every image when it is dynamically appended to the parent container of all the Mr. Potato Head parts. The dragging functionality works perfectly, however the resizing functionality does not work at all. I would love help in figuring out the best way to make these images dynamically resizable by the user. I have attached a picture of how the website looks on the screen since the code does not run properly without the folder of images in the directory.

<head>
    <style>
        body {
            /*background-image: url(images/background3.jpg);*/
            display: grid;
            grid-template-columns: 25% 75%;
            column-gap: 5em;
        }

        #mrPotatoHead {
            width: 75%;
        }

        #bodyPartDiv {
            border: 2px solid black;
        }

        .part {
            max-width: 100%;
            height: auto;
        }
    </style>
</head>

<body>
    <audio xautoplay loop src="media/Children's Music - Kids Game.mp3"></audio>

    <div id="bodyPartDiv"></div>
    <img id="mrPotatoHead" src="images/mr-potato-head/body.png">

    <script src="game.js"></script>
</body>

async function loadParts() {
        try {
            const response = await fetch('parts.json');
            if (!response.ok) {
                throw new Error(`${response.status} - ${response.statusText}`);
            }
            const json = await response.json();
            return json.images;
        } catch (e) {
            console.error(e);
        }
    }

    const parts = await loadParts();
    parts.forEach(part => {
        const img = document.createElement('img');
        img.src = part;
        img.className = 'part';
        partDiv.append(img);
        dragAndResizeElement(img);
    });

    function dragAndResizeElement(element) {
        let dragging;
        let resizing;
        let translate = { x: 0, y: 0 };
        let client = { x: 0, y: 0 };
        let originalWidth, originalHeight;

        element.addEventListener('mousedown', e => {
            e.preventDefault();
            if (e.offsetX > (element.offsetWidth - 20) && e.offsetY > (element.offsetHeight - 20)) {
                element.style.cursor = 'nw-resize';
                resizing = true;
                originalWidth = element.offsetWidth;
                originalHeight = element.offsetHeight;
            }
            else {
                element.style.cursor = 'grabbing';
                dragging = true;
            }
            client.x = e.clientX;
            client.y = e.clientY;
        });


        document.addEventListener('mousemove', e => {
            e.preventDefault();
            if (dragging) {
                translate.x = translate.x - (client.x - e.clientX);
                translate.y = translate.y - (client.y - e.clientY);
                element.style.transform = `translate(${translate.x}px, ${translate.y}px)`;
            }
            else if (resizing) {
                const newWidth = originalWidth + (e.clientX - client.x);
                const newHeight = originalHeight + (e.clientY - client.y);
                element.style.width = `${newWidth}px`;
                element.style.height = `${newHeight}px`;
            }
            client.x = e.clientX;
            client.y = e.clientY;

        });

        document.addEventListener('mouseup', () => {
            element.style.cursor = 'grab';
            dragging = false;
            resizing = false;
        });
    }

Website View

TypeError: Failed to fetch, 400 error in network console, but not in exception

I am attempting to call the Google Drive REST API to upload a file from a browser context using the fetch api, but am not able to progress past a weird error.

TypeError: Failed to fetch

Looking at other posts on the topic, some point to a CORS failure, but it’s pretty difficult to determine exactly what the error is from this message. The strange thing is, looking at the Network tab of the Chrome devtools tells a different story:

enter image description here

Now I’ll be the first to admit that there’s a (high?) chance I’m sending a malformed request, but it’s also pretty difficult to determine what about the request is malformed without any error message.

What I’m doubly confused by, is why the fetch call is rejecting (throws error with await), rather than resolving with a 400 response.

The code to make the call is about as basic as it gets:

await fetch(`https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`, options);

The options object is pretty boiler-plate, and just sets things like an Authorization header, Content-Type, Content-Length, as well as the body which (in this case) is multipart/related

So I neither get a graceful resolve response with a message, nor do I get useful reject outcome, rather just TypeError: Failed to fetch

If I were to rely on the rejection alone, I would have no idea this was actually a 400. It was only the Network tools that revealed this.

Anyone have a clue why this 400 error would be “swallowed”, and what I need to do to capture it?

Additional Edits

Further detail in response to comments. The code around the fetch looks like this:

async send() {
  this._build();
  return await fetch(this._parameterizedUrl, this._options);
}

This is contained in an object which just encapsulates the process of creating a request (called a Request). So the this pointer refers to the encapsulating object. this._parameterizedUrl is just the value of the URL string already posted, and this._options is the options object already mentioned. The call to build() just assembles the values of the Request object, mostly into the options argument (but also supports URL parameters, not used in this case)

The code calling this looks like:

const request = Request.post(uploadUrl);
request.setContentType(contentType);
request.setBody(body);
request.setAccessToken(accessToken);
request.addHeader('Content-Length', contentLength);
const response = await request.send();

The Request.post(uploadUrl) call is a shorthand to create my Request object (this is my own encapsulation as mentioned)

This call will throw an error, which is caught at an earlier point in the stack. The next line of the trace from the Failed to fetch error looks like this:

TypeError: Failed to fetch
    at Request.send (api.js:240:1)

Full Reproduction

document.addEventListener('DOMContentLoaded', async () => {
  
  const url = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart';
  const options = {};
  const authToken = '<Add Auth Token>';
  const boundary = 'boundary-' + Math.random().toString(36).substring(2);
  const contentType = `multipart/related; boundary=${boundary}`;

  // Body part meta data for the filename
  const meta = {
    name: 'test.json'
  };

  // Body part for media (the file)
  const data = {
    foo: "bar",
    bar: "baz"
  }

  const metaBlob = new Blob([JSON.stringify(meta)], {
    type: 'application/json',
  });

  const mediaBlob = new Blob([JSON.stringify(data)], {
    type: 'application/json',
  });

  const body = new FormData();
  body.append('metadata', metaBlob, "metadata");
  body.append('media', mediaBlob, name);
  let contentLength = metaBlob.size + mediaBlob.size;

  const headers = new Headers();
  headers.append('Authorization', `Bearer ${authToken}`);
  headers.append('Content-Type', contentType);
  headers.append('Content-Length', contentLength);

  options['method'] = 'POST';
  options['headers'] = headers;
  options['body'] = body;

  try {
    const response = await fetch(url, options);
    console.log(response);
  } catch (err) {
    console.error(err);
  }
});

Identify iframe in k6 browser

I see the iframe gets inserted into DOM after I click on some icon. So DOM gets updated dynamically. Once I am able to get this iframe element, I need to operate on some elements within it. There are also some shadow host elements within this iframe.

I tried following:
Attempt1:

const iframe = page.waitForSelector("#iframeId")

Attempt2:

const iFrameHandle = page.waitForSelector("#parentelement")
const iframe = iFrameHandle.contentFrame()

Attempt3:

page.mainFrame(), page.frames()

Last resort:

const iframeHandle = await page.waitForSelector(“#iframeId”, { state: ‘attached’, timeout: 10000 });
const iframe = await iframeHandle.contentFrame();
console.log("IFRAME: "+iframe);
console.log(“Iframe handle stringify:”, JSON.stringify(iframeHandle, null, 2));

Is there anything I need to add to the code after I click on the icon which loads the iframe and before I try to locate elements within the iframe ?

Is there any equivalent in k6 for switch to iframe in Selenium ?

Hide svg custom cursor over iframe video embed

Similar to this question, but I couldn’t make the answers work with my existing code. I have a custom cursor which is an inline svg, it works great everywhere (and scrolls with the page) but gets stuck at the edge of the iframe from a Vimeo embed. I’d like to hide it when the mouse enters the iframe and bring it back when it exits. How can I adjust my code below to make the last part of the JS work correctly?

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
    let lastX, lastY, lastScrolled = 0;;
    $(document).on('mousemove', function(e) {
      lastX = e.pageX - 1;
      lastY = e.pageY - 1;
      $('#cursor').css({
        left: e.pageX - 1,
        top: e.pageY - 1,
        pointerEvents: 'none'
      });
    });

    $(window).scroll(function(event) {
      if (lastScrolled != $(window)
        .scrollTop()) {
        lastY -= lastScrolled;
        lastScrolled = $(window)
          .scrollTop();
        lastY += lastScrolled;
      }
      $('#cursor').css({
        left: lastX,
        top: lastY,
        pointerEvents: 'none'
      });
    });
var iframe = document.querySelector("iframe");
iframe.addEventListener("mouseover", function() {
  cursor.style.display = 'none';
})
iframe.addEventListener("mouseleave", function() {
  cursor.style.display = 'block';
})
</script>
body,html { cursor: none !important; }
#cursor {display: block; position: absolute; z-index: 9999; pointer-events:none; 
    width: 1.2rem; height: auto; } 
/*  width: 6.666rem; */
@media only screen and (max-width: 960px) {
    * { cursor: default !important; }
    #cursor{display: none;}
}
#cursor .cursorshape {fill: #d9e021;}

.embedvid {position: relative; height: 0; overflow: hidden; max-width: 100%;} 
.embedvid iframe, .embedvid object, .embedvid embed {position: absolute; top: 0; left: 0; width: 100%; height: 100%;}
.ratio-16-9 {padding-bottom: 56.25%;}
<svg version="1.1" id="cursor" style="width:1.2rem;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 30 38" enable-background="new 0 0 30 38" xml:space="preserve">
    <polygon class="cursorshape" points="29.397,31.059 29.397,28.54 29.397,24.939 29.397,22.42 29.397,18.819 29.397,16.3 29.397,10.18 
    0.603,0.822 0.603,6.942 0.603,9.46 0.603,13.062 0.603,15.581 0.603,19.181 0.603,21.701 0.603,27.82 29.397,37.178 "/>
</svg>

<div style="width:50%">
<div class="embedvid ratio-16-9">
        <iframe src='https://player.vimeo.com/video/766323945' frameborder='0' webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
    </div>
  </div>

after i change page the dark mode switch dosent work

Hello im working on a small website and i have a small problem i have a theme switch button that turn the page to dark mode but if i am in the home page and change to contact page the theme switch stops working but this happens only on the other pages exept the main one and i dont know how to fix it i have tried to mess around with the js code but nothing

Here is my JavaScript code:

// DarkMode
let darkmode = localStorage.getItem("darkmode")
const themeSwitch = document.getElementById("theme-switch")

const enableDarkmode = () => {
    document.body.classList.add("darkmode")
    localStorage.setItem("darkmode", "active")
    document.getElementById("logo").src="../Imgs/logo-white.png"
}

const disableDarkmode = () => {
    document.body.classList.remove("darkmode")
    localStorage.setItem("darkmode", null)
    document.getElementById("logo").src="../Imgs/logo-blue.png";
}

if (darkmode ==="active") enableDarkmode()

themeSwitch.addEventListener("click", () => {
    darkmode = localStorage.getItem("darkmode")

    darkmode !== "active" ? enableDarkmode() : disableDarkmode()
})

merge sort using react for random number data

I am trying to learn React by following the below video.
For some reason nothing printing inside div tag.
can you let me know how to fix it.
Providing the code and sandbox below.
I debugged by putting console but still no luck

https://www.youtube.com/watch?v=pFXYym4Wbkc&list=PL3lfepputJuGAaI3S7FvcBIutzHNtZ_pb&index=1
https://codesandbox.io/p/sandbox/l4q4pz?file=%2Fsrc%2FsortingVisualizer%2FsortingVisualizer.jsx%3A1%2C1-37%2C1

import React from "react";

const number_of_array_bars = 310;

const sortingVisualizer = () => {
  const [array, setArray] = useState([]);

  useEffect(() => {}, []);
  const resetArray = () => {
    const newArray = [];
    for (let i = 0; i < number_of_array_bars; i++) {
      newArray.push(randomIntFromInterval(5, 730));
    }
    console.log("newArray", newArray);
    setArray(newArray);
  };
  const render = () => {
    return (
      <>
        {console.log("array", array)}
        {array.map((value, index) => {
          <div key={index} style={{ height: "${value}px" }}>
            test
          </div>;
        })}
      </>
    );
  };
  return render();
};

export default sortingVisualizer;

function randomIntFromInterval(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

Nuxt and vuewordcloud error – 500 Unexpected token ‘:’

I have a web application in Vue.js using nuxt and vuetify.

I have installed the library vuewordcloud to make a word cloud : https://www.npmjs.com/package/vuewordcloud

And now, when I start the project, it indicates an error : `500 Unexpected token ‘:’`.

Here is the full error message :

500
Unexpected token ':'

    at new Script (node:vm:99:7)
    at createScript (node:vm:255:10)
    at Object.runInThisContext (node:vm:303:10)
    at ViteNodeRunner.runModule (./node_modules/vite-node/dist/client.mjs:398:19)
    at ViteNodeRunner.directRequest (./node_modules/vite-node/dist/client.mjs:381:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async ViteNodeRunner.cachedRequest (./node_modules/vite-node/dist/client.mjs:206:14)
    at async ViteNodeRunner.dependencyRequest (./node_modules/vite-node/dist/client.mjs:259:12)
    at async ./plugins/wordcloud.ts:2:31
    at async ViteNodeRunner.runModule (./node_modules/vite-node/dist/client.mjs:399:5)

Here is my plugins/wordcloud.ts configuration :

import { defineNuxtPlugin } from '#app';
import VueWordCloud from 'vuewordcloud';

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.component('VueWordCloud', VueWordCloud);
});

I’ve tried to use the same configuration but in Javascript.

But when I try to do that, the same error appears.

Is there any other way to make a wordcloud in Vue.js or any way to solve this problem ?

using createElement React/next js

In my Next js public folder i have a file let’s call it script.js and my intention is to be able to call a react component from my components folder inside of the script.js file. so i came across the createElement from the react docs which i’m using to try and achieve this but i continue to encounter this error ReferenceError: Greeting is not defined and By Name, “Greeting” from the ReferenceError is the name of the component i’m trying to use. Now below is my sample public/script.js file

(function () {

const container= document.createElement('div');
container.id = 'chatbot-root';
document.body.appendChild(chatbotContainer);

// CDN REACT 18
function loadReact() {
return Promise.all([
  loadScript('https://unpkg.com/react@18/umd/react.production.min.js'),
  loadScript('https://unpkg.com/react-dom@18/umd/react-dom.production.min.js')
 ]);
}

function loadScript(src) {
return new Promise((resolve, reject) => {
  const script = document.createElement('script');
  script.src = src;
  script.onload = resolve;
  script.onerror = reject;
  document.head.appendChild(script);
});
}

fetch(`http:exampl.com`) 
.then(response => {
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
})

.then(data => {
  return loadReact().then(() => {
    renderReactApp(data);
  });
})
.catch(error => console.error('Error loading chatbot:', error));


function renderReactApp(data) {
  const root = document.getElementById('chatbot-root');
  if (root) {
    const root = ReactDOM.createRoot(root);
   // The Greetings you see here is the name of the component
   return root.render(React.createElement( Greeting, { name: 'Taylor' }
  ));
  } else {
    console.error('container not found');
  }
}
})();

And here is the code for the Greetings Component

export function Greeting({ name }:{name:string}) {
return (
<h1 className="greeting">
  Hello <i>{name}</i>. Welcome!
</h1>
);
}
declare global {
 interface Window {
   Greeting: typeof Greeting;
}
}

if (typeof window !== 'undefined') {
 window.Greeting = Greeting;
}

As you can see on the Greetings component, one thing i’ve been trying to do is make the component global so hopefully, it can be assessed in the public/script.js all to no avail.And also i created a @types folder in the root of the project and added the file global.d.ts which contain the following below just to make sure it’s global

declare global {
  interface Window {
    Greeting: Greeting; 
 }
}

Descpite all that i still ge the error ReferenceError: Greeting is not defined to the best of my understanding of the docs with regards to the createElement, what i’ve done so far should work just fine but it isn’t. Thanks

Testing api calls with Vitest and Axios returns a 403 error

I’ve got a bunch of Api calls running with Axios but when my app starts I run this function to get authentication token stored in Axios as a default header:

export const myAuth = async () => {
  return new Promise(async (resolve, reject) => {
    try {
      const loginOptions = {
        email: import.meta.env.VITE_USER,
        password: import.meta.env.VITE_PASSWORD
      }
      const login = await axios.post(myUrl + '/auth/login', loginOptions)

      axios.defaults.headers.common['Authorization'] = 'Bearer ' + login.data.data.access_token

      resolve(login)
    } catch (error) {
      reject(error)
    }
  })
}

When I attempt to run any api calls in Vitest in order to test them, I’m getting a 403 error, even though my axios instance shows the auth token is there. Any ideas of what I’m getting wrong?

Here is an example of a test that is failing:

import { describe, test, expect } from 'vitest'
import { myAuth, getUserItems } from '@/apiCalls.js'

await myAuth()

describe('api calls', () => {
  test('get user items', async () => {
    const data = await getUserItems()
    expect(data).toBeDefined()
  })
})

When I run this test, getUserItems() fails with a 403 error, when I’ve already gotten the auth token added as a default header in myAuth().

How can I separate the navigation bar and the content without damaging it?

I tried to create a navigation bar for a site.

And then I try to add some content from a known set of ready-made styles.

I try to add several elements
but they overlap or remove each other’s style or elements.

How can I make the navigation bar separately independent and be able to add content elements from a known set of ready-made styles without damaging the navigation bar and themselves?

How can I separate the navigation bar and the content from a set of ready-made styles without damaging it?

index.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sidebar Menu | Side Navigation Bar</title>
    <!-- CSS -->
    <link rel="stylesheet" href="{% static "css/style.css" %}" />
    <!-- Boxicons CSS -->
    <link
      href="https://unpkg.com/[email protected]/css/boxicons.min.css"
      rel="stylesheet"
    />
    <!-- You MUST include jQuery 3.4+ before Fomantic -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css">
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.js"></script>
  </head>
  <body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
    <nav>
      <div class="logo">
        <i class="bx bx-menu menu-icon"></i>
        <span class="logo-name">InfoEco</span>
      </div>
      <div class="sidebar">
        <div class="logo">
          <i class="bx bx-menu menu-icon"></i>
          <span class="logo-name">InfoEco</span>
        </div>
        <div class="sidebar-content">
          <ul class="lists">
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-home-alt icon"></i>
                <span class="link">Home</span>
              </a>
            </li>
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-bar-chart-alt-2 icon"></i>
                <span class="link">Label Nav Menu 1</span>
              </a>
              <div class="sub-menu">
                <a href="#">Label Nav Sub Menu 1</a>
                <a href="#">Label Nav Sub Menu 2</a>
                <a href="#">Label Nav Sub Menu 3</a>
                <a href="#">Label Nav Sub Menu 4</a>
              </div>
            </li>
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-bell icon"></i>
                <span class="link">Label Nav Menu 2</span>
              </a>
              <div class="sub-menu">
                <a href="#">Label Nav Sub Menu 1</a>
                <a href="#">Label Nav Sub Menu 2</a>
              </div>
            </li>
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-heart icon"></i>
                <span class="link">Label Nav Menu 3</span>
              </a>
            </li>
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-folder-open icon"></i>
                <span class="link">Label Nav Menu 4</span>
              </a>
            </li>
          </ul>

          <div class="bottom-cotent">
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-cog icon"></i>
                <span class="link">Label Nav Menu 5</span>
              </a>
            </li>
            <li class="list">
              <a href="#" class="nav-link">
                <i class="bx bx-log-out icon"></i>
                <span class="link">Label Nav Menu 6</span>
              </a>
            </li>
          </div>
        </div>
      </div>
    </nav>

    <section class="overlay"></section> 
    <div class="ui divider"></div>
    <div class="ui divider"></div>
    <div class="ui divider"></div>
    
    <div class="ui container">
      <div class="ui grid">
        <div class="four wide column">
          <div class="ui vertical menu">
            <a class="item">
              <h4 class="ui header">Promotions</h4>
              <p>Check out our new promotions</p>
            </a>
            <a class="item">
              <h4 class="ui header">Coupons</h4>
              <p>Check out our collection of coupons</p>
            </a>
            <a class="item">
              <h4 class="ui header">Rebates</h4>
              <p>Visit our rebate forum for information on claiming rebates</p>
            </a>
          </div>
        </div>
        <div class="twelve wide column">content</div>
      </div>
    </div>
    <script src="{% static "script/script.js" %}"></script>
  </body>
</html>

style.css

/* Google Fonts - Poppins */
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap");

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: "Poppins", sans-serif;
}
body {
  min-height: 100%;
  background: #e3f2fd;
}
nav {
  position: fixed;
  top: 0;
  left: 0;
  height: 70px;
  width: 100%;
  display: flex;
  align-items: center;
  background: #fff;
  box-shadow: 0 0 1px rgba(0, 0, 0, 0.1);
}
nav .logo {
  display: flex;
  align-items: center;
  margin: 0 24px;
}
.logo .menu-icon {
  color: #333;
  font-size: 24px;
  margin-right: 14px;
  cursor: pointer;
}
.logo .logo-name {
  color: #333;
  font-size: 22px;
  font-weight: 500;
}
nav .sidebar {
  position: fixed;
  top: 0;
  left: -100%;
  height: 100%;
  width: 260px;
  padding: 20px 0;
  background-color: #fff;
  box-shadow: 0 5px 1px rgba(0, 0, 0, 0.1);
  transition: all 0.4s ease;
}
nav.open .sidebar {
  left: 0;
}
.sidebar .sidebar-content {
  display: flex;
  height: 100%;
  flex-direction: column;
  justify-content: space-between;
  padding: 30px 16px;
}
.sidebar-content .list {
  list-style: none;
}
.list .nav-link {
  display: flex;
  align-items: center;
  margin: 8px 0;
  padding: 14px 12px;
  border-radius: 8px;
  text-decoration: none;
}
.lists .nav-link:hover {
  background: linear-gradient(#dfe4e6, #eff2f4);
}
.nav-link .icon {
  margin-right: 14px;
  font-size: 20px;
  color: #707070;
}
.nav-link .link {
  font-size: 16px;
  color: #707070;
  font-weight: 400;
}
.lists .nav-link:hover .icon,
.lists .nav-link:hover .link {
  color: #4b6a78;
  font-weight: 500;
}
.overlay {
  position: fixed;
  top: 0;
  left: -100%;
  height: 1000vh;
  width: 200%;
  opacity: 0;
  pointer-events: none;
  transition: all 0.4s ease;
  background: rgba(0, 0, 0, 0.3);
}
nav.open ~ .overlay {
  opacity: 1;
  left: 260px;
  pointer-events: auto;
}
nav .sidebar .sidebar-content .list .sub-menu {
  margin-left: 5px;
  padding-left: 10px;
  border-left: 1px solid #bbb;
  max-height: 0px;
  overflow: hidden;
  transition: max-height 300ms ease-in-out;
}
nav .sidebar .sidebar-content .list.active .sub-menu {
  max-height: 500px;
}
nav .sidebar .sidebar-content .list .sub-menu a {
  text-decoration: none;
  display: block;
  padding: 5px;
  margin: 5px 0px;
  font-size: 16px;
  color: #707070;
  font-weight: 400;
  cursor: pointer;
}
nav .sidebar .sidebar-content .list .sub-menu a:hover {
  background: linear-gradient(#dfe4e6, #eff2f4);
  color: #4b6a78;
  font-weight: 500;
}

script.js

const navBar = document.querySelector("nav"),
       menuBtns = document.querySelectorAll(".menu-icon"),
       overlay = document.querySelector(".overlay");

     menuBtns.forEach((menuBtn) => {
       menuBtn.addEventListener("click", () => {
         navBar.classList.toggle("open");
       });
     });

     overlay.addEventListener("click", () => {
       navBar.classList.remove("open");
     });

document.querySelectorAll("nav .sidebar .sidebar-content .list a").forEach(function(list){
  list.addEventListener("click",function(e){
    if(e.target.parentNode.children.length > 1){
      e.target.parentNode.classList.toggle("active");
    }
  });
});

enter image description here

enter image description here

enter image description here

enter image description here

onSuccess not running after link token is created

I’ve been trying to create a small app that differentiates between Zelle transactions and the actual balance. Of course in order to do that I need bank information. Thus, I am trying to integrate the plaid sdk into my react native ios app.

I am trying to follow this repo’s implementation but I am struggling with one thing.

When my link token is created, I am struggling to get the onSuccess to run so I can have the exchange link token api get called. Does anyone know how I can do this in IOS?

import React, { useState, useEffect, useCallback } from 'react';
import {
  View,
  Text,
  StyleSheet,
  Button,
  TouchableOpacity,
} from 'react-native';

import { create, open, dismissLink, LinkSuccess, LinkExit, LinkIOSPresentationStyle, LinkLogLevel } from 'react-native-plaid-link-sdk';

// Define the types for transactions
type Transaction = {
  id: string;
  amount: number;
  date: string; // ISO format date string
  type: 'credit' | 'debit';
};

// Mock data for demonstration purposes
const mockTransactions: Transaction[] = [
  // Add your transaction data here
];

const BalancePage: React.FC = () => {
  console.log('Rendering BalancePage');
  const [filter, setFilter] = useState<string>('month');
  const [filterFromPayout, setFilterFromPayout] = useState<boolean>(false);
  const [lastPayoutDate, setLastPayoutDate] = useState<string | null>(null);
  const [totalBalance, setTotalBalance] = useState<number>(0);
  const [zelleBalance, setZelleBalance] = useState<number>(0);
  const [linkToken, setLinkToken] = useState<string | null>(null);

  const handlePayout = () => {
    const payoutDate = new Date().toISOString();
    console.log(`Setting last payout date to: ${payoutDate}`);
    setLastPayoutDate(payoutDate);
    setFilterFromPayout(true);
  };


  const createLinkToken = useCallback(async () => {
    await fetch(`http://localhost:8000/api/create_link_token/`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ address: 'localhost:8000' })
    })
      .then((response) => response.json())
      .then((data) => {
        setLinkToken(data.link_token);
      })
      .catch((err) => {
        console.log(err);
      });
  }, [setLinkToken]);

  const createLinkTokenConfiguration = (token: string, noLoadingState: boolean = false) => {
    return {
      token: token,
      noLoadingState: noLoadingState,
    };
  };
  useEffect(() => {
    console.log('useEffect triggered. linkToken:', linkToken);
    if (linkToken === null) {
      console.log('Getting link token');
      createLinkToken();
    }
    if (linkToken) {
      const tokenConfig = createLinkTokenConfiguration(linkToken);
      create(tokenConfig)
    }
  }, [filterFromPayout, linkToken]);

  const createLinkOpenProps = () => {
    return {
      onSuccess: async (success: LinkSuccess) => {
        await fetch(`http://localhost:8000/api/exchange_link_token/`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ public_token: success.publicToken }),
        })
          .catch((err) => {
            console.log(err);
          });
      },
      onExit: (linkExit: LinkExit) => {
        console.log('Exit: ', linkExit);
        dismissLink();
      },
      iOSPresentationStyle: LinkIOSPresentationStyle.MODAL,
      logLevel: LinkLogLevel.ERROR,
    };
  };

  const handleOpenLink = () => {
    const openProps = createLinkOpenProps();
    open(openProps);
  };


  const filters = ['day', 'week', 'month', 'monthToDate'];

  const calculateBalances = () => {
    console.log('Calculating balances...');
    let filteredTransactions = filterTransactions(mockTransactions, filter);

    if (filterFromPayout && lastPayoutDate) {
      console.log(`Filtering transactions from last payout date: ${lastPayoutDate}`);
      filteredTransactions = filteredTransactions.filter(
        txn => new Date(txn.date) > new Date(lastPayoutDate)
      );
    }

    const balance = filteredTransactions.reduce((acc, txn) => {
      return txn.type === 'credit' ? acc + txn.amount : acc - txn.amount;
    }, 0);

    const zelleTxns = filteredTransactions.filter(txn => txn.type === 'debit'); // Assuming 'debit' as Zelle transactions
    const zelleTotal = zelleTxns.reduce((acc, txn) => acc + txn.amount, 0);

    console.log(`New total balance: ${balance}, New Zelle balance: ${zelleTotal}`);
    setTotalBalance(balance);
    setZelleBalance(zelleTotal);
  };

  const filterTransactions = (transactions: Transaction[], filter: string) => {
    console.log(`Filtering transactions with filter: ${filter}`);
    const now = new Date();
    return transactions.filter(txn => {
      const txnDate = new Date(txn.date);
      switch (filter) {
        case 'day':
          return isSameDay(now, txnDate);
        case 'week':
          return isSameWeek(now, txnDate);
        case 'month':
          return isSameMonth(now, txnDate);
        case 'monthToDate':
          return txnDate >= new Date(now.getFullYear(), now.getMonth(), 1);
        default:
          return true;
      }
    });
  };

  const isSameDay = (d1: Date, d2: Date) => {
    const result = d1.getDate() === d2.getDate() &&
      d1.getMonth() === d2.getMonth() &&
      d1.getFullYear() === d2.getFullYear();
    console.log(`Comparing dates: ${d1.toISOString()} and ${d2.toISOString()}. Same day: ${result}`);
    return result;
  }

  const isSameWeek = (d1: Date, d2: Date) => {
    const onejan = new Date(d1.getFullYear(), 0, 1);
    const week1 = Math.ceil((((d1.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
    const week2 = Math.ceil((((d2.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
    const result = week1 === week2 && d1.getFullYear() === d2.getFullYear();
    console.log(`Comparing dates: ${d1.toISOString()} and ${d2.toISOString()}. Same week: ${result}`);
    return result;
  };

  const isSameMonth = (d1: Date, d2: Date) => {
    const result = d1.getMonth() === d2.getMonth() && d1.getFullYear() === d2.getFullYear();
    console.log(`Comparing dates: ${d1.toISOString()} and ${d2.toISOString()}. Same month: ${result}`);
    return result;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Total Balance</Text>
      <Text style={styles.balance}>${totalBalance.toFixed(2)}</Text>

      <Text style={styles.subtitle}>Filters</Text>
      <View style={styles.filterContainer}>
        {filters.map(f => (
          <TouchableOpacity
            key={f}
            style={[
              styles.filterButton,
              filter === f && styles.activeFilter
            ]}
            onPress={() => {
              console.log(`Filter selected: ${f}`);
              setFilter(f);
            }}
          >
            <Text style={styles.filterText}>{f.charAt(0).toUpperCase() + f.slice(1)}</Text>
          </TouchableOpacity>
        ))}
      </View>

      <Text style={styles.subtitle}>Zelle Transactions</Text>
      <Text style={styles.balance}>${zelleBalance.toFixed(2)}</Text>

      <Button title="Paid out" onPress={handlePayout} />
      <Button title="Connect Bank" onPress={handleOpenLink} />

      {filterFromPayout && (
        <Text style={styles.payoutFilter}>
          From last paid out: {new Date(lastPayoutDate!).toLocaleString()}
        </Text>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginVertical: 8,
  },
  balance: {
    fontSize: 32,
    fontWeight: 'bold',
    textAlign: 'center',
    marginVertical: 8,
    color: '#4CAF50',
  },
  subtitle: {
    fontSize: 20,
    fontWeight: '600',
    marginTop: 16,
    marginBottom: 8,
  },
  filterContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 16,
  },
  filterButton: {
    padding: 10,
    borderRadius: 5,
    backgroundColor: '#E0E0E0',
  },
  activeFilter: {
    backgroundColor: '#4CAF50',
  },
  filterText: {
    color: '#000',
  },
  payoutFilter: {
    marginTop: 16,
    textAlign: 'center',
    color: '#FF5722',
  },
});

export default BalancePage;

I’ve tried removing the onSuccess, but that doesnt work. I’ve tried using PlaidLink library, but that is deprecated. I’ve tried ChatGPT (lol) but that also just told me to add better logging to debug.