From 7ea6706181caa665039490f73bbb91f32c46ba3a Mon Sep 17 00:00:00 2001 From: prxt6529 Date: Tue, 10 Mar 2026 11:33:06 +0200 Subject: [PATCH 01/11] Share modal update Signed-off-by: prxt6529 --- .../header/share/HeaderShare.test.tsx | 10 +- .../header/share/HeaderShare.module.scss | 32 -- components/header/share/HeaderShare.tsx | 321 ++++++++++++------ 3 files changed, 218 insertions(+), 145 deletions(-) delete mode 100644 components/header/share/HeaderShare.module.scss diff --git a/__tests__/components/header/share/HeaderShare.test.tsx b/__tests__/components/header/share/HeaderShare.test.tsx index e9b19dd038..63666608d2 100644 --- a/__tests__/components/header/share/HeaderShare.test.tsx +++ b/__tests__/components/header/share/HeaderShare.test.tsx @@ -233,7 +233,7 @@ describe("HeaderShare", () => { }); describe("Authentication State Handling", () => { - it("shows Share Connection tab when authenticated", async () => { + it("shows Connection tab when authenticated", async () => { mockUseCapacitor.mockReturnValue({ isCapacitor: false } as any); mockIsMobile.mockReturnValue(false); @@ -260,8 +260,8 @@ describe("HeaderShare", () => { await screen.findByTestId("header-share-modal"); - // Should show Share Connection button when authenticated - expect(screen.getByText("Share Connection")).toBeInTheDocument(); + // Should show Connection button when authenticated + expect(screen.getByText("Connection")).toBeInTheDocument(); expect(screen.getByText("Current URL")).toBeInTheDocument(); expect(screen.getByText("6529 Apps")).toBeInTheDocument(); }); @@ -291,8 +291,8 @@ describe("HeaderShare", () => { await screen.findByTestId("header-share-modal"); - // Should NOT show Share Connection button when not authenticated - expect(screen.queryByText("Share Connection")).not.toBeInTheDocument(); + // Should NOT show Connection button when not authenticated + expect(screen.queryByText("Connection")).not.toBeInTheDocument(); expect(screen.getByText("Current URL")).toBeInTheDocument(); expect(screen.getByText("6529 Apps")).toBeInTheDocument(); }); diff --git a/components/header/share/HeaderShare.module.scss b/components/header/share/HeaderShare.module.scss deleted file mode 100644 index a1299fd247..0000000000 --- a/components/header/share/HeaderShare.module.scss +++ /dev/null @@ -1,32 +0,0 @@ -@use "../../../styles/variables.scss"; - -.disabledMenuBtn { - pointer-events: none; -} - -.modalBody { - background-color: variables.$very-dark-grey; -} - -.url { - font-size: smaller; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: variables.$lightest-grey; - max-width: calc(100% - 20px); -} - -.urlCopy { - height: 16px; - cursor: pointer; - color: variables.$lightest-grey; - - &:hover { - color: variables.$off-white; - } - - &.copied { - color: green; - } -} diff --git a/components/header/share/HeaderShare.tsx b/components/header/share/HeaderShare.tsx index dccffc6ec0..95ddabd5c0 100644 --- a/components/header/share/HeaderShare.tsx +++ b/components/header/share/HeaderShare.tsx @@ -8,7 +8,6 @@ import yaml from "js-yaml"; import Image from "next/image"; import { usePathname, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; -import { Button, Modal } from "react-bootstrap"; import { Tooltip } from "react-tooltip"; import useIsMobileDevice from "@/hooks/isMobileDevice"; import useCapacitor from "@/hooks/useCapacitor"; @@ -20,7 +19,6 @@ import { getWalletRole, } from "@/services/auth/auth.utils"; import { useSeizeConnectContext } from "@/components/auth/SeizeConnectContext"; -import styles from "./HeaderShare.module.scss"; import { ShareMobileApp } from "./HeaderShareMobileApps"; const QRCode = require("qrcode"); @@ -39,8 +37,6 @@ enum SubMode { const squareStyle = { width: "100%", - maxWidth: "1000px", - aspectRatio: "1 / 1", display: "flex", alignItems: "center", justifyContent: "center", @@ -106,9 +102,11 @@ export function HeaderQRModal({ }) { const pathname = usePathname(); const searchParams = useSearchParams(); - const isMobile = useIsMobileDevice(); + const [shouldRender, setShouldRender] = useState(show); + const [isVisible, setIsVisible] = useState(show); + const { isAuthenticated } = useSeizeConnectContext(); const [activeTab, setActiveTab] = useState( @@ -212,25 +210,60 @@ export function HeaderQRModal({ return () => clearTimeout(timer); }, [show]); + useEffect(() => { + if (show) { + setShouldRender(true); + const raf = requestAnimationFrame(() => setIsVisible(true)); + return () => cancelAnimationFrame(raf); + } + + setIsVisible(false); + const timeout = setTimeout(() => setShouldRender(false), 200); + return () => clearTimeout(timeout); + }, [show]); + + useEffect(() => { + if (!shouldRender) { + return; + } + + const previousOverflow = document.body.style.overflow; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onClose(); + } + }; + + document.body.style.overflow = "hidden"; + window.addEventListener("keydown", onKeyDown); + + return () => { + document.body.style.overflow = previousOverflow; + window.removeEventListener("keydown", onKeyDown); + }; + }, [shouldRender, onClose]); + function printImage() { const renderQRCodeImage = (src: string, alt: string) => { - const defaultStyle = { - maxWidth: "100%", - height: "auto", - border: "5px solid #fff", - }; + const normalizedSrc = src?.trim(); + return ( - {alt} +
+ {normalizedSrc ? ( + {alt} + ) : ( +
+ )} +
); }; @@ -251,13 +284,10 @@ export function HeaderQRModal({ height={150} className="unselectable" /> - +
); @@ -326,60 +356,100 @@ export function HeaderQRModal({ } return ( - <> - {content} - {url && ( -
-
{url}
- +
+
+ {content} +
+
+ {url ? ( +
+
+ {url} +
+
+ ) : ( +
)} - +
); } + if (!shouldRender) { + return null; + } + return ( - - - { - setActiveTab(tab); - setActiveSubTab(subTab); - }} - /> - {printImage()} - - +
); } @@ -395,64 +465,99 @@ function ModalMenu({ readonly onTabChange: (tab: Mode, subTab: SubMode) => void; }) { const isElectron = useElectron(); + const topTabCount = isShareConnection ? 3 : 2; + const subTabCount = + activeTab === Mode.NAVIGATE ? (!isElectron ? 3 : 2) : !isElectron ? 2 : 1; + const subTabLabel = + activeTab === Mode.APPS + ? "Select Platform" + : activeTab === Mode.SHARE + ? "Open Link In" + : "Open URL In"; + const getMenuButtonClass = (active: boolean) => + `tw-inline-flex tw-h-10 tw-w-full tw-min-w-0 tw-items-center tw-justify-center tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-rounded-xl tw-border-0 tw-px-2 tw-text-[15px] tw-font-medium tw-transition tw-duration-200 ${ + active + ? "tw-bg-iron-700 tw-text-iron-50" + : "tw-bg-iron-900 tw-text-iron-400 hover:tw-bg-iron-800 hover:tw-text-iron-100" + }`; return ( -
-
- {isShareConnection && ( - - )} - - + {isShareConnection && ( + + )} + + +
-
- - {activeTab === Mode.NAVIGATE && ( - - )} - {!isElectron && ( - - )} + 6529 Mobile + + {activeTab === Mode.NAVIGATE && ( + + )} + {!isElectron && ( + + )} +
); @@ -597,7 +702,7 @@ function CoreAppDownload({ />
-
+
{platform} v{version}
From 0e53ac01449a53eee59e98786d278c48e120793d Mon Sep 17 00:00:00 2001 From: prxt6529 Date: Tue, 10 Mar 2026 11:58:11 +0200 Subject: [PATCH 02/11] WIP Signed-off-by: prxt6529 --- components/header/share/HeaderShare.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/header/share/HeaderShare.tsx b/components/header/share/HeaderShare.tsx index 95ddabd5c0..ab70050e4a 100644 --- a/components/header/share/HeaderShare.tsx +++ b/components/header/share/HeaderShare.tsx @@ -357,7 +357,7 @@ export function HeaderQRModal({ return (
-
+
{content}
@@ -365,7 +365,7 @@ export function HeaderQRModal({ {url ? (
{url} @@ -375,10 +375,14 @@ export function HeaderQRModal({ aria-label="Copy URL" className="tw-inline-flex tw-h-8 tw-w-8 tw-items-center tw-justify-center tw-rounded-md tw-border-0 tw-bg-transparent tw-text-iron-400 tw-transition-colors hover:tw-bg-iron-800 hover:tw-text-iron-100" data-tooltip-id="copy-url-tooltip" - onClick={() => { - navigator.clipboard.writeText(url); - setUrlCopied(true); - setTimeout(() => setUrlCopied(false), 500); + onClick={async () => { + try { + await navigator.clipboard.writeText(url); + setUrlCopied(true); + setTimeout(() => setUrlCopied(false), 500); + } catch (error) { + console.error("Failed to copy share URL to clipboard", error); + } }} > Date: Tue, 10 Mar 2026 12:07:58 +0200 Subject: [PATCH 03/11] WIP Signed-off-by: prxt6529 --- components/header/share/HeaderShare.tsx | 273 +++++++++++++----------- 1 file changed, 148 insertions(+), 125 deletions(-) diff --git a/components/header/share/HeaderShare.tsx b/components/header/share/HeaderShare.tsx index ab70050e4a..0231064e86 100644 --- a/components/header/share/HeaderShare.tsx +++ b/components/header/share/HeaderShare.tsx @@ -42,6 +42,23 @@ const squareStyle = { justifyContent: "center", }; +function getSubTabCount(activeTab: Mode, isElectron: boolean): number { + if (activeTab === Mode.NAVIGATE) { + return isElectron ? 2 : 3; + } + return isElectron ? 1 : 2; +} + +function getSubTabLabel(activeTab: Mode): string { + if (activeTab === Mode.APPS) { + return "Select Platform"; + } + if (activeTab === Mode.SHARE) { + return "Open Link In"; + } + return "Open URL In"; +} + export default function HeaderShare({ isCollapsed = false, }: { @@ -235,126 +252,136 @@ export function HeaderQRModal({ }; document.body.style.overflow = "hidden"; - window.addEventListener("keydown", onKeyDown); + globalThis.addEventListener("keydown", onKeyDown); return () => { document.body.style.overflow = previousOverflow; - window.removeEventListener("keydown", onKeyDown); + globalThis.removeEventListener("keydown", onKeyDown); }; }, [shouldRender, onClose]); - function printImage() { - const renderQRCodeImage = (src: string, alt: string) => { - const normalizedSrc = src?.trim(); - - return ( -
- {normalizedSrc ? ( - {alt} - ) : ( -
- )} -
- ); + const renderQRCodeImage = (src: string, alt: string) => { + const normalizedSrc = src?.trim(); + + return ( +
+ {normalizedSrc ? ( + {alt} + ) : ( +
+ )} +
+ ); + }; + + const renderCoreLink = (url: string) => { + return ( + + ); + }; + + const getNavigateContent = () => { + if (activeSubTab === SubMode.BROWSER) { + return { + content: renderQRCodeImage(navigateBrowserSrc, "Browser Link - QR Code"), + url: navigateBrowserUrl, + }; + } + + if (activeSubTab === SubMode.CORE) { + return { + content: renderCoreLink(navigateCoreUrl), + url: navigateCoreUrl, + }; + } + + return { + content: renderQRCodeImage(navigateAppSrc, "Mobile App Link - QR Code"), + url: navigateAppUrl, }; + }; + + const getShareContent = () => { + if (activeSubTab === SubMode.CORE) { + return { + content: renderCoreLink(shareConnectionCoreUrl), + url: shareConnectionCoreUrl, + }; + } - const renderCoreLink = (url: string) => { - return ( -
- - 6529 Desktop -
- -
Open in 6529 Desktop
-
-
+ if (activeSubTab === SubMode.APP) { + return { + content: renderQRCodeImage(shareConnectionSrc, "Share Connection - QR Code"), + url: shareConnectionAppUrl, + }; + } + + return { content: Invalid submode for SHARE, url: "" }; + }; + + const getAppsContent = () => { + if (activeSubTab === SubMode.CORE) { + return { content: , url: "" }; + } + + return { + content: ( +
+ +
- ); + ), + url: "", }; + }; - let content = null; - let url = ""; - + const getDisplayContent = () => { if (activeTab === Mode.NAVIGATE) { - switch (activeSubTab) { - case SubMode.BROWSER: - url = navigateBrowserUrl; - content = renderQRCodeImage( - navigateBrowserSrc, - "Browser Link - QR Code" - ); - break; - case SubMode.APP: - url = navigateAppUrl; - content = renderQRCodeImage( - navigateAppSrc, - "Mobile App Link - QR Code" - ); - break; - case SubMode.CORE: - url = navigateCoreUrl; - content = renderCoreLink(navigateCoreUrl); - break; - default: - break; - } - } else if (activeTab === Mode.SHARE) { - switch (activeSubTab) { - case SubMode.APP: - url = shareConnectionAppUrl; - content = renderQRCodeImage( - shareConnectionSrc, - "Share Connection - QR Code" - ); - break; - case SubMode.CORE: - content = renderCoreLink(shareConnectionCoreUrl); - url = shareConnectionCoreUrl; - break; - default: - content = Invalid submode for SHARE; - break; - } - } else if (activeTab === Mode.APPS) { - switch (activeSubTab) { - case SubMode.APP: - content = ( -
- - -
- ); - break; - case SubMode.CORE: - content = ; - break; - } + return getNavigateContent(); } + if (activeTab === Mode.SHARE) { + return getShareContent(); + } + + return getAppsContent(); + }; + + function printImage() { + const { content, url } = getDisplayContent(); + return (
@@ -429,16 +456,14 @@ export function HeaderQRModal({ className="tw-absolute tw-inset-0 tw-border-0 tw-bg-transparent" onClick={onClose} /> -
event.stopPropagation()} >
{printImage()}
-
+
); } @@ -470,20 +495,18 @@ function ModalMenu({ }) { const isElectron = useElectron(); const topTabCount = isShareConnection ? 3 : 2; - const subTabCount = - activeTab === Mode.NAVIGATE ? (!isElectron ? 3 : 2) : !isElectron ? 2 : 1; - const subTabLabel = - activeTab === Mode.APPS - ? "Select Platform" - : activeTab === Mode.SHARE - ? "Open Link In" - : "Open URL In"; - const getMenuButtonClass = (active: boolean) => - `tw-inline-flex tw-h-10 tw-w-full tw-min-w-0 tw-items-center tw-justify-center tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-rounded-xl tw-border-0 tw-px-2 tw-text-[15px] tw-font-medium tw-transition tw-duration-200 ${ - active - ? "tw-bg-iron-700 tw-text-iron-50" - : "tw-bg-iron-900 tw-text-iron-400 hover:tw-bg-iron-800 hover:tw-text-iron-100" - }`; + const subTabCount = getSubTabCount(activeTab, isElectron); + const subTabLabel = getSubTabLabel(activeTab); + const getMenuButtonClass = (active: boolean) => { + const baseClassName = + "tw-inline-flex tw-h-10 tw-w-full tw-min-w-0 tw-items-center tw-justify-center tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-rounded-xl tw-border-0 tw-px-2 tw-text-[15px] tw-font-medium tw-transition tw-duration-200"; + + if (active) { + return `${baseClassName} tw-bg-iron-700 tw-text-iron-50`; + } + + return `${baseClassName} tw-bg-iron-900 tw-text-iron-400 hover:tw-bg-iron-800 hover:tw-text-iron-100`; + }; return (
From dce4044a3f1fe75828426819beeda887ae553172 Mon Sep 17 00:00:00 2001 From: prxt6529 Date: Tue, 10 Mar 2026 12:15:36 +0200 Subject: [PATCH 04/11] WIP Signed-off-by: prxt6529 --- components/header/share/HeaderShare.tsx | 58 +++++++++++++++++-------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/components/header/share/HeaderShare.tsx b/components/header/share/HeaderShare.tsx index 0231064e86..9aa4867719 100644 --- a/components/header/share/HeaderShare.tsx +++ b/components/header/share/HeaderShare.tsx @@ -7,7 +7,7 @@ import { ShareIcon } from "@heroicons/react/24/outline"; import yaml from "js-yaml"; import Image from "next/image"; import { usePathname, useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Tooltip } from "react-tooltip"; import useIsMobileDevice from "@/hooks/isMobileDevice"; import useCapacitor from "@/hooks/useCapacitor"; @@ -144,6 +144,17 @@ export function HeaderQRModal({ const [shareConnectionSrc, setShareConnectionSrc] = useState(""); const [urlCopied, setUrlCopied] = useState(false); + const onCloseRef = useRef(onClose); + + useEffect(() => { + onCloseRef.current = onClose; + }, [onClose]); + + const handleEscapeKeyDown = useCallback((event: KeyboardEvent) => { + if (event.key === "Escape") { + onCloseRef.current(); + } + }, []); function generateSources( refreshToken: string | null, @@ -245,20 +256,15 @@ export function HeaderQRModal({ } const previousOverflow = document.body.style.overflow; - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - onClose(); - } - }; document.body.style.overflow = "hidden"; - globalThis.addEventListener("keydown", onKeyDown); + globalThis.addEventListener("keydown", handleEscapeKeyDown); return () => { document.body.style.overflow = previousOverflow; - globalThis.removeEventListener("keydown", onKeyDown); + globalThis.removeEventListener("keydown", handleEscapeKeyDown); }; - }, [shouldRender, onClose]); + }, [shouldRender, handleEscapeKeyDown]); const renderQRCodeImage = (src: string, alt: string) => { const normalizedSrc = src?.trim(); @@ -458,6 +464,7 @@ export function HeaderQRModal({ />
+

+ Share +