From a677e8687db35d0abe38a9a54f3e9ce5b9a8b207 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 18 Jun 2026 20:48:11 +0700 Subject: [PATCH] fix: Amber Capacitor APK + mobile UI improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amber login (critical): - Replace window.location.href with window.open(_system) in Capacitor WebViews - Capacitor's WebView throws "malformed nostrsigner request" on href navigation - New openAmberUrl() helper detects Capacitor via window.Capacitor.isNativePlatform() - Add openAmberForLoginNoCallback() for PWA/Capacitor clipboard flow - Fix all 6 Amber URL opens in amber.ts + LoginDialog.tsx Mobile nav: - Newsletter and Sasquatch AI (/pyramid) added to hamburger menu - Sasquatch floating guide now shows on mobile (removed hidden md:block) Lab page mobile: - Action buttons (Offer Coaching/Filming/Editing/Get Card) now 2-col grid on mobile - Service type filter (Coaches/Filmers/Editors) now 3-col grid, full width - Reduced top padding on mobile (py-6 → py-12 on md+) - Text truncates cleanly, icons don't overflow Wallet page mobile: - Reduced padding (py-6 md:py-8) - Tab triggers use h-auto + py-2.5 to avoid overflow - Smaller header text on mobile Co-authored-by: shakespeare.diy --- src/components/Layout.tsx | 28 ++++++++++---- src/components/auth/LoginDialog.tsx | 24 +++++------- src/lib/amber.ts | 38 ++++++++++++++++--- src/pages/LabPage.tsx | 59 +++++++++++++++-------------- src/pages/WalletPage.tsx | 24 ++++++------ 5 files changed, 105 insertions(+), 68 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 0257b32..9483a52 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -45,6 +45,8 @@ import { Zap, Heart, CreditCard, + Radio, + Bot, } from 'lucide-react'; import { OfflineIndicator } from '@/components/OfflineIndicator'; import { UserStatusIndicator } from '@/components/UserStatusIndicator'; @@ -680,6 +682,20 @@ export function Layout({ children }: LayoutProps) { isActive={isActive('/supporters')} onClick={() => setMobileMenuOpen(false)} /> + setMobileMenuOpen(false)} + /> + setMobileMenuOpen(false)} + /> - {/* Sasquatch Guide (Desktop only) */} -
- -
+ {/* Sasquatch Guide (all screens) */} + {/* Set Status Dialog */} = ({ isOpen, onClose, onLogin, onS || ('standalone' in window.navigator && (window.navigator as { standalone?: boolean }).standalone === true); if (isPWA) { - // ── PWA mode ────────────────────────────────────────────────────────── - // When installed to home screen, the app is single-tab. Opening - // nostrsigner: with a callbackUrl sends the callback to the browser, - // not back to the PWA — so the PWA never sees the result. - // - // Instead: open Amber WITHOUT a callbackUrl. Amber copies the pubkey - // to clipboard when there's no callback. When the user comes back, - // the visibilitychange fires and we auto-read the clipboard. - const amberUrl = 'nostrsigner:?compressionType=none&returnType=signature&type=get_public_key'; - window.location.href = amberUrl; + // ── PWA / Capacitor mode ─────────────────────────────────────────────── + // When installed to home screen or running as a Capacitor APK, the app + // is single-tab. Use openAmberForLogin() which handles Capacitor WebViews + // correctly via window.open(_system) instead of window.location.href. + // Amber copies the pubkey to clipboard when there's no callback. + // When the user comes back, visibilitychange fires and we read the clipboard. + openAmberForLoginNoCallback(); // When user returns to the PWA after approving in Amber, auto-read clipboard unsubAmberRef.current = subscribeToAmberReturn(async () => { @@ -166,9 +163,8 @@ const LoginDialog: React.FC = ({ isOpen, onClose, onLogin, onS unsubAmberRef.current = null; }); - const callbackUrl = `${window.location.origin}/amber-callback?event=`; - const amberUrl = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=${encodeURIComponent(callbackUrl)}`; - window.location.href = amberUrl; + // Browser (tab) mode — use the callback URL flow + openAmberForLogin(); }; const handleBunkerLogin = async () => { diff --git a/src/lib/amber.ts b/src/lib/amber.ts index 10b0fe6..d3d682e 100644 --- a/src/lib/amber.ts +++ b/src/lib/amber.ts @@ -246,6 +246,32 @@ function buildCallbackUrl(): string { return `${window.location.origin}/amber-callback?event=`; } +/** + * Navigate to an Amber nostrsigner: URL. + * In Capacitor WebViews, window.location.href causes a "malformed request" + * error because the WebView tries to navigate instead of firing an intent. + * Use window.open() with _system target to hand off to Android properly. + */ +function openAmberUrl(url: string): void { + // Capacitor / Android WebView: use _system to fire the intent + const isCapacitor = !!(window as unknown as { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor?.isNativePlatform?.(); + if (isCapacitor) { + window.open(url, '_system'); + } else { + window.location.href = url; + } +} + +/** + * Open Amber for get_public_key WITHOUT a callback URL. + * Used in PWA/Capacitor mode: Amber copies pubkey to clipboard, + * the app reads it on visibilitychange. + */ +export function openAmberForLoginNoCallback(): void { + const amberUrl = 'nostrsigner:?compressionType=none&returnType=signature&type=get_public_key'; + openAmberUrl(amberUrl); +} + /** * Open Amber for get_public_key operation. * Uses a dedicated /amber-callback route so the app state is preserved @@ -267,7 +293,7 @@ export function openAmberForLogin(): void { const amberUrl = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=${encodeURIComponent(callbackUrl)}`; console.log('[Amber] Opening Amber for login, callback:', callbackUrl); - window.location.href = amberUrl; + openAmberUrl(amberUrl); } /** @@ -309,7 +335,7 @@ export function openAmberForSignEvent(event: Omit): vo const encodedEvent = encodeURIComponent(eventJson); const amberUrl = `nostrsigner:${encodedEvent}?compressionType=none&returnType=event&type=sign_event&callbackUrl=${encodeURIComponent(callbackUrl)}`; - window.location.href = amberUrl; + openAmberUrl(amberUrl); } /** @@ -330,7 +356,7 @@ export function openAmberForNip04Encrypt(pubkey: string, plaintext: string): voi const encodedText = encodeURIComponent(plaintext); const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip04_encrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`; - window.location.href = amberUrl; + openAmberUrl(amberUrl); } /** @@ -351,7 +377,7 @@ export function openAmberForNip04Decrypt(pubkey: string, ciphertext: string): vo const encodedText = encodeURIComponent(ciphertext); const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip04_decrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`; - window.location.href = amberUrl; + openAmberUrl(amberUrl); } /** @@ -372,7 +398,7 @@ export function openAmberForNip44Encrypt(pubkey: string, plaintext: string): voi const encodedText = encodeURIComponent(plaintext); const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip44_encrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`; - window.location.href = amberUrl; + openAmberUrl(amberUrl); } /** @@ -393,7 +419,7 @@ export function openAmberForNip44Decrypt(pubkey: string, ciphertext: string): vo const encodedText = encodeURIComponent(ciphertext); const amberUrl = `nostrsigner:${encodedText}?pubkey=${pubkey}&compressionType=none&returnType=signature&type=nip44_decrypt&callbackUrl=${encodeURIComponent(callbackUrl)}`; - window.location.href = amberUrl; + openAmberUrl(amberUrl); } /** diff --git a/src/pages/LabPage.tsx b/src/pages/LabPage.tsx index 369b2a0..fd6c231 100644 --- a/src/pages/LabPage.tsx +++ b/src/pages/LabPage.tsx @@ -102,8 +102,8 @@ export default function LabPage() { return ( -
-
+
+

THE LAB @@ -111,29 +111,29 @@ export default function LabPage() {

{t('labPage.subtitle')}

{user ? ( -
- - - - - - - -
@@ -145,7 +145,7 @@ export default function LabPage() {
{/* Service Type Filter */} -
+
diff --git a/src/pages/WalletPage.tsx b/src/pages/WalletPage.tsx index 8afcf78..bffd73f 100644 --- a/src/pages/WalletPage.tsx +++ b/src/pages/WalletPage.tsx @@ -295,29 +295,29 @@ export default function WalletPage() { return ( -
+
{/* Header */} -
-

Wallet

-

+

+

Wallet

+

Manage your Bitcoin and send zaps to skaters and creators

{user ? ( -
+
{/* Wallet Comparison Guide */} - - - - Cashu Wallet + + + + Cashu Wallet - - - Lightning + + + Lightning