From 4810aef4b2707f1dd8ffbc99d434929801e256c7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:49:05 +0000 Subject: [PATCH 01/10] Add fallback copy/paste code flow for OAuth when deep link redirect fails When desktop/mobile users authenticate via OAuth, the deep link redirect back to the app sometimes fails (e.g., opens in Safari instead). This adds a fallback mechanism: - Web side (auth callback): After 3 seconds, shows a 'Copy Code' button with a base64-encoded auth token that users can copy to clipboard - Desktop side (login/signup): Adds a 'Paste Login Code' option in Tauri environments where users can paste the copied code to complete auth The code is a base64-encoded JSON containing access_token and refresh_token. Co-Authored-By: unknown <> --- .../src/routes/auth.$provider.callback.tsx | 71 +++++++++++- frontend/src/routes/login.tsx | 105 ++++++++++++++++- frontend/src/routes/signup.tsx | 109 +++++++++++++++++- 3 files changed, 277 insertions(+), 8 deletions(-) diff --git a/frontend/src/routes/auth.$provider.callback.tsx b/frontend/src/routes/auth.$provider.callback.tsx index 4687e8e1..cddc7dcd 100644 --- a/frontend/src/routes/auth.$provider.callback.tsx +++ b/frontend/src/routes/auth.$provider.callback.tsx @@ -1,9 +1,9 @@ import { createFileRoute, useNavigate, Link } from "@tanstack/react-router"; -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState, useRef, useCallback } from "react"; import { useOpenSecret } from "@opensecret/react"; import { AlertDestructive } from "@/components/AlertDestructive"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Loader2 } from "lucide-react"; +import { Loader2, Copy, Check, ExternalLink } from "lucide-react"; import { Button } from "@/components/ui/button"; import { getBillingService } from "@/billing/billingService"; @@ -28,10 +28,28 @@ function formatProviderName(provider: string): string { function OAuthCallback() { const [isProcessing, setIsProcessing] = useState(true); const [error, setError] = useState(null); + const [showCopyFallback, setShowCopyFallback] = useState(false); + const [copied, setCopied] = useState(false); + const [authCode, setAuthCode] = useState(null); const navigate = useNavigate(); const { handleGitHubCallback, handleGoogleCallback, handleAppleCallback } = useOpenSecret(); const processedRef = useRef(false); + const handleCopyCode = useCallback(async () => { + if (!authCode) return; + try { + await navigator.clipboard.writeText(authCode); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + // Fallback: select the text in the input for manual copy + const input = document.querySelector("[data-auth-code]"); + if (input) { + input.select(); + } + } + }, [authCode]); + // Helper functions for the callback process const handleSuccessfulAuth = () => { // Check if this is a Tauri app auth flow (desktop or mobile) @@ -45,16 +63,29 @@ function OAuthCallback() { const accessToken = localStorage.getItem("access_token") || ""; const refreshToken = localStorage.getItem("refresh_token"); + // Generate the fallback auth code for copy/paste flow + const codePayload = JSON.stringify({ + access_token: accessToken, + refresh_token: refreshToken || "" + }); + setAuthCode(btoa(codePayload)); + let deepLinkUrl = `cloud.opensecret.maple://auth?access_token=${encodeURIComponent(accessToken)}`; if (refreshToken) { deepLinkUrl += `&refresh_token=${encodeURIComponent(refreshToken)}`; } + // Attempt the deep link redirect setTimeout(() => { window.location.href = deepLinkUrl; }, 1000); + // Show the copy fallback after a few seconds in case the redirect doesn't work + setTimeout(() => { + setShowCopyFallback(true); + }, 3000); + return; } @@ -153,7 +184,7 @@ function OAuthCallback() { }, [handleGitHubCallback, handleGoogleCallback, handleAppleCallback, navigate, provider]); // If this is a Tauri app auth flow (desktop or mobile), show a different UI - if (localStorage.getItem("redirect-to-native") === "true") { + if (localStorage.getItem("redirect-to-native") === "true" || authCode) { return ( @@ -161,9 +192,41 @@ function OAuthCallback() {

Authentication successful! Redirecting you back to the app...

-
+
+ {showCopyFallback && authCode && ( +
+

+ + Not redirecting to the app? Copy the code below and paste it in the Maple app. +

+
+ (e.target as HTMLInputElement).select()} + /> + +
+

+ In the Maple app, tap "Paste Login Code" on the login screen. +

+
+ )} ); diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 47822ae7..bdd274fe 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { AlertDestructive } from "@/components/AlertDestructive"; -import { Loader2, Github, Mail, UserCircle } from "lucide-react"; +import { Loader2, Github, Mail, UserCircle, ClipboardPaste } from "lucide-react"; import { Google } from "@/components/icons/Google"; import { Apple } from "@/components/icons/Apple"; import { AuthMain } from "@/components/AuthMain"; @@ -33,7 +33,7 @@ export const Route = createFileRoute("/login")({ }) }); -type LoginMethod = "email" | "github" | "google" | "apple" | "guest" | null; +type LoginMethod = "email" | "github" | "google" | "apple" | "guest" | "paste-code" | null; function LoginPage() { const navigate = useNavigate(); @@ -43,6 +43,8 @@ function LoginPage() { const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [pasteCodeValue, setPasteCodeValue] = useState(""); + // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); @@ -105,6 +107,46 @@ function LoginPage() { } }; + const handlePasteCode = async () => { + setIsLoading(true); + setError(null); + try { + const decoded = atob(pasteCodeValue.trim()); + const parsed = JSON.parse(decoded) as { access_token?: string; refresh_token?: string }; + + if (!parsed.access_token || !parsed.refresh_token) { + throw new Error("Invalid login code: missing tokens"); + } + + // Store tokens in localStorage + localStorage.setItem("access_token", parsed.access_token); + localStorage.setItem("refresh_token", parsed.refresh_token); + + // Clear any existing billing token + try { + getBillingService().clearToken(); + } catch (billingError) { + console.warn("Failed to clear billing token:", billingError); + } + + // Reload the app to pick up the new tokens + window.location.href = "/"; + } catch (err) { + if ( + err instanceof SyntaxError || + (err instanceof DOMException && err.name === "InvalidCharacterError") + ) { + setError("Invalid login code. Please copy the code from the browser and try again."); + } else if (err instanceof Error) { + setError(err.message); + } else { + setError("Failed to process login code. Please try again."); + } + } finally { + setIsLoading(false); + } + }; + const handleGitHubLogin = async () => { try { console.log("[OAuth] Using", isTauriEnv ? "Tauri" : "web", "flow"); @@ -391,6 +433,12 @@ function LoginPage() { Log in as Anonymous + {isTauriEnv && ( + + )}
Need an account?{" "} @@ -401,6 +449,59 @@ function LoginPage() { ); } + if (loginMethod === "paste-code") { + return ( + + {error && } +
+ + setPasteCodeValue(e.target.value)} + className="font-mono text-xs" + autoFocus + /> +

+ After signing in with your browser, copy the code shown on the success page and paste it + here. +

+
+ + +
+ ); + } + if (loginMethod === "guest") { return (
diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 7fc581cb..6f6c322d 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { AlertDestructive } from "@/components/AlertDestructive"; -import { Loader2, Github, Mail } from "lucide-react"; +import { Loader2, Github, Mail, ClipboardPaste } from "lucide-react"; import { Google } from "@/components/icons/Google"; import { Apple } from "@/components/icons/Apple"; import { AuthMain } from "@/components/AuthMain"; @@ -36,7 +36,7 @@ export const Route = createFileRoute("/signup")({ }) }); -type SignUpMethod = "email" | "github" | "google" | "apple" | "guest" | null; +type SignUpMethod = "email" | "github" | "google" | "apple" | "guest" | "paste-code" | null; function SignupPage() { const navigate = useNavigate(); @@ -49,6 +49,8 @@ function SignupPage() { const [showGuestCredentials, setShowGuestCredentials] = useState(false); const [guestUuid, setGuestUuid] = useState(null); + const [pasteCodeValue, setPasteCodeValue] = useState(""); + // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); @@ -116,6 +118,46 @@ function SignupPage() { } }; + const handlePasteCode = async () => { + setIsLoading(true); + setError(null); + try { + const decoded = atob(pasteCodeValue.trim()); + const parsed = JSON.parse(decoded) as { access_token?: string; refresh_token?: string }; + + if (!parsed.access_token || !parsed.refresh_token) { + throw new Error("Invalid login code: missing tokens"); + } + + // Store tokens in localStorage + localStorage.setItem("access_token", parsed.access_token); + localStorage.setItem("refresh_token", parsed.refresh_token); + + // Clear any existing billing token + try { + getBillingService().clearToken(); + } catch (billingError) { + console.warn("Failed to clear billing token:", billingError); + } + + // Reload the app to pick up the new tokens + window.location.href = "/"; + } catch (err) { + if ( + err instanceof SyntaxError || + (err instanceof DOMException && err.name === "InvalidCharacterError") + ) { + setError("Invalid login code. Please copy the code from the browser and try again."); + } else if (err instanceof Error) { + setError(err.message); + } else { + setError("Failed to process login code. Please try again."); + } + } finally { + setIsLoading(false); + } + }; + const handleGitHubSignup = async () => { try { console.log("[OAuth] Using", isTauriEnv ? "Tauri" : "web", "flow"); @@ -407,6 +449,16 @@ function SignupPage() { Sign up as Anonymous + {isTauriEnv && ( + + )}
Already have an account?{" "} + {error && } +
+ + setPasteCodeValue(e.target.value)} + className="font-mono text-xs" + autoFocus + /> +

+ After signing in with your browser, copy the code shown on the success page and paste it + here. +

+
+ + + + ); + } + if (signUpMethod === "guest") { return ( <> From 0e02bd7140cf7dbe66524b61718a6368b659af3d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:53:47 +0000 Subject: [PATCH 02/10] Fix: only require access_token in paste code validation The refresh_token may be an empty string when generated by the callback page (defaults to '' when absent from localStorage). The previous check treated empty string as falsy, rejecting valid codes. Now only access_token is required, and refresh_token is conditionally stored. Co-Authored-By: unknown <> --- frontend/src/routes/login.tsx | 8 +++++--- frontend/src/routes/signup.tsx | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index bdd274fe..61d613f1 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -114,13 +114,15 @@ function LoginPage() { const decoded = atob(pasteCodeValue.trim()); const parsed = JSON.parse(decoded) as { access_token?: string; refresh_token?: string }; - if (!parsed.access_token || !parsed.refresh_token) { - throw new Error("Invalid login code: missing tokens"); + if (!parsed.access_token) { + throw new Error("Invalid login code: missing token"); } // Store tokens in localStorage localStorage.setItem("access_token", parsed.access_token); - localStorage.setItem("refresh_token", parsed.refresh_token); + if (parsed.refresh_token) { + localStorage.setItem("refresh_token", parsed.refresh_token); + } // Clear any existing billing token try { diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 6f6c322d..08c94650 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -125,13 +125,15 @@ function SignupPage() { const decoded = atob(pasteCodeValue.trim()); const parsed = JSON.parse(decoded) as { access_token?: string; refresh_token?: string }; - if (!parsed.access_token || !parsed.refresh_token) { - throw new Error("Invalid login code: missing tokens"); + if (!parsed.access_token) { + throw new Error("Invalid login code: missing token"); } // Store tokens in localStorage localStorage.setItem("access_token", parsed.access_token); - localStorage.setItem("refresh_token", parsed.refresh_token); + if (parsed.refresh_token) { + localStorage.setItem("refresh_token", parsed.refresh_token); + } // Clear any existing billing token try { From 3d6a626889a6ff35f4b4f3c0330ec3918e802054 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:59:46 +0000 Subject: [PATCH 03/10] Fix: clear stale refresh_token before conditionally setting new one When pasting a login code with an empty refresh_token, the old refresh_token in localStorage was not being cleared, which could cause cross-session token mismatches on refresh. Co-Authored-By: unknown <> --- frontend/src/routes/login.tsx | 3 ++- frontend/src/routes/signup.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index 61d613f1..86ed0bc0 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -118,8 +118,9 @@ function LoginPage() { throw new Error("Invalid login code: missing token"); } - // Store tokens in localStorage + // Store tokens in localStorage (clear old refresh token to prevent stale mismatch) localStorage.setItem("access_token", parsed.access_token); + localStorage.removeItem("refresh_token"); if (parsed.refresh_token) { localStorage.setItem("refresh_token", parsed.refresh_token); } diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 08c94650..6f453aed 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -129,8 +129,9 @@ function SignupPage() { throw new Error("Invalid login code: missing token"); } - // Store tokens in localStorage + // Store tokens in localStorage (clear old refresh token to prevent stale mismatch) localStorage.setItem("access_token", parsed.access_token); + localStorage.removeItem("refresh_token"); if (parsed.refresh_token) { localStorage.setItem("refresh_token", parsed.refresh_token); } From d77ee8d2c46128a02c20da7aba2943f77b71bb53 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 18:14:42 +0000 Subject: [PATCH 04/10] UX: auto-navigate to paste-code screen after OAuth button click on desktop - After clicking GitHub/Google/Apple OAuth on desktop, the app now automatically shows the paste-code screen with a spinner and 'Complete your [Provider] login in the browser' messaging - After 3 seconds, reveals the paste code input with 'Having trouble?' - Updated web callback fallback text to 'Trouble opening the app?' - Applied consistently to both login.tsx and signup.tsx Co-Authored-By: unknown <> --- .../src/routes/auth.$provider.callback.tsx | 2 +- frontend/src/routes/login.tsx | 110 +++++++++++++----- frontend/src/routes/signup.tsx | 110 +++++++++++++----- 3 files changed, 157 insertions(+), 65 deletions(-) diff --git a/frontend/src/routes/auth.$provider.callback.tsx b/frontend/src/routes/auth.$provider.callback.tsx index cddc7dcd..fe298aa0 100644 --- a/frontend/src/routes/auth.$provider.callback.tsx +++ b/frontend/src/routes/auth.$provider.callback.tsx @@ -199,7 +199,7 @@ function OAuthCallback() {

- Not redirecting to the app? Copy the code below and paste it in the Maple app. + Trouble opening the app? Copy the code below and paste it in the Maple app.

(null); + const [showPasteInput, setShowPasteInput] = useState(false); // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); + // Show paste code input after a delay when auto-navigated from OAuth + useEffect(() => { + if (loginMethod === "paste-code" && oauthProvider) { + setShowPasteInput(false); + const timer = setTimeout(() => setShowPasteInput(true), 3000); + return () => clearTimeout(timer); + } + if (loginMethod === "paste-code" && !oauthProvider) { + setShowPasteInput(true); + } + }, [loginMethod, oauthProvider]); + // Redirect if already logged in useEffect(() => { if (os.auth.user) { @@ -175,6 +189,10 @@ function LoginPage() { console.error("[OAuth] Failed to open external browser:", error); setError("Failed to open authentication page in browser"); }); + + // Navigate to paste-code screen so user sees it while browser is open + setOauthProvider("GitHub"); + setLoginMethod("paste-code"); } else { // Web flow remains unchanged const { auth_url } = await os.initiateGitHubAuth(""); @@ -217,6 +235,10 @@ function LoginPage() { console.error("[OAuth] Failed to open external browser:", error); setError("Failed to open authentication page in browser"); }); + + // Navigate to paste-code screen so user sees it while browser is open + setOauthProvider("Google"); + setLoginMethod("paste-code"); } else { // Web flow remains unchanged const { auth_url } = await os.initiateGoogleAuth(""); @@ -381,6 +403,10 @@ function LoginPage() { console.error("[OAuth] Failed to open external browser:", error); setError("Failed to open authentication page in browser"); }); + + // Navigate to paste-code screen so user sees it while browser is open + setOauthProvider("Apple"); + setLoginMethod("paste-code"); } else { // Web flow - use AppleAuthProvider component which will initiate the flow console.log("[OAuth] Using web flow for Apple Sign In (Web only)"); @@ -455,46 +481,66 @@ function LoginPage() { if (loginMethod === "paste-code") { return ( + {oauthProvider && !showPasteInput && ( +
+ +
+ )} {error && } -
- - setPasteCodeValue(e.target.value)} - className="font-mono text-xs" - autoFocus - /> -

- After signing in with your browser, copy the code shown on the success page and paste it - here. -

-
- + {showPasteInput && ( + <> + {oauthProvider && ( +

+ Having trouble? Paste the login code from the browser below. +

+ )} +
+ + setPasteCodeValue(e.target.value)} + className="font-mono text-xs" + autoFocus + /> +

+ After signing in with your browser, copy the code shown on the success page and + paste it here. +

+
+ + + )} + {showPasteInput && ( + <> + {oauthProvider && ( +

+ Having trouble? Paste the login code from the browser below. +

+ )} +
+ + setPasteCodeValue(e.target.value)} + className="font-mono text-xs" + autoFocus + /> +

+ After signing in with your browser, copy the code shown on the success page and + paste it here. +

+
+ + + )} - {isTauriEnv && ( - - )}
Need an account?{" "} diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 090aac5a..9b2e6f57 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { AlertDestructive } from "@/components/AlertDestructive"; -import { Loader2, Github, Mail, ClipboardPaste } from "lucide-react"; +import { Loader2, Github, Mail } from "lucide-react"; import { Google } from "@/components/icons/Google"; import { Apple } from "@/components/icons/Apple"; import { AuthMain } from "@/components/AuthMain"; @@ -481,16 +481,6 @@ function SignupPage() { Sign up as Anonymous - {isTauriEnv && ( - - )}
Already have an account?{" "} Date: Wed, 25 Feb 2026 21:29:35 +0000 Subject: [PATCH 07/10] Gate paste-code auto-navigation to desktop only (not iOS/Android) Use isTauriDesktop() instead of isTauri() for the paste-code screen auto-navigation after OAuth button click. The browser opening still happens on all Tauri platforms, but the paste-code fallback UI is only shown on desktop where deep link redirects are less reliable. Co-Authored-By: unknown <> --- frontend/src/routes/login.tsx | 33 ++++++++++++++++++++------------- frontend/src/routes/signup.tsx | 33 ++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index f92ec62f..de0d89a3 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -16,7 +16,7 @@ import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; import { AppleAuthProvider } from "@/components/AppleAuthProvider"; import { getBillingService } from "@/billing/billingService"; -import { isIOS, isTauri } from "@/utils/platform"; +import { isIOS, isTauri, isTauriDesktop } from "@/utils/platform"; type LoginSearchParams = { next?: string; @@ -50,6 +50,7 @@ function LoginPage() { // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); + const isTauriDesktopEnv = isTauriDesktop(); // Show paste code input after a delay when auto-navigated from OAuth useEffect(() => { @@ -190,10 +191,12 @@ function LoginPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open - setError(null); - setOauthProvider("GitHub"); - setLoginMethod("paste-code"); + // Navigate to paste-code screen so user sees it while browser is open (desktop only) + if (isTauriDesktopEnv) { + setError(null); + setOauthProvider("GitHub"); + setLoginMethod("paste-code"); + } } else { // Web flow remains unchanged const { auth_url } = await os.initiateGitHubAuth(""); @@ -237,10 +240,12 @@ function LoginPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open - setError(null); - setOauthProvider("Google"); - setLoginMethod("paste-code"); + // Navigate to paste-code screen so user sees it while browser is open (desktop only) + if (isTauriDesktopEnv) { + setError(null); + setOauthProvider("Google"); + setLoginMethod("paste-code"); + } } else { // Web flow remains unchanged const { auth_url } = await os.initiateGoogleAuth(""); @@ -406,10 +411,12 @@ function LoginPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open - setError(null); - setOauthProvider("Apple"); - setLoginMethod("paste-code"); + // Navigate to paste-code screen so user sees it while browser is open (desktop only) + if (isTauriDesktopEnv) { + setError(null); + setOauthProvider("Apple"); + setLoginMethod("paste-code"); + } } else { // Web flow - use AppleAuthProvider component which will initiate the flow console.log("[OAuth] Using web flow for Apple Sign In (Web only)"); diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 9b2e6f57..c5d16404 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -16,7 +16,7 @@ import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; import { AppleAuthProvider } from "@/components/AppleAuthProvider"; import { getBillingService } from "@/billing/billingService"; -import { isIOS, isTauri } from "@/utils/platform"; +import { isIOS, isTauri, isTauriDesktop } from "@/utils/platform"; import { GuestSignupWarningDialog } from "@/components/GuestSignupWarningDialog"; import { GuestCredentialsDialog } from "@/components/GuestCredentialsDialog"; import { UserCircle } from "lucide-react"; @@ -56,6 +56,7 @@ function SignupPage() { // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); + const isTauriDesktopEnv = isTauriDesktop(); // Show paste code input after a delay when auto-navigated from OAuth useEffect(() => { @@ -201,10 +202,12 @@ function SignupPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open - setError(null); - setOauthProvider("GitHub"); - setSignUpMethod("paste-code"); + // Navigate to paste-code screen so user sees it while browser is open (desktop only) + if (isTauriDesktopEnv) { + setError(null); + setOauthProvider("GitHub"); + setSignUpMethod("paste-code"); + } } else { // Web flow remains unchanged const { auth_url } = await os.initiateGitHubAuth(""); @@ -248,10 +251,12 @@ function SignupPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open - setError(null); - setOauthProvider("Google"); - setSignUpMethod("paste-code"); + // Navigate to paste-code screen so user sees it while browser is open (desktop only) + if (isTauriDesktopEnv) { + setError(null); + setOauthProvider("Google"); + setSignUpMethod("paste-code"); + } } else { // Web flow remains unchanged const { auth_url } = await os.initiateGoogleAuth(""); @@ -422,10 +427,12 @@ function SignupPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open - setError(null); - setOauthProvider("Apple"); - setSignUpMethod("paste-code"); + // Navigate to paste-code screen so user sees it while browser is open (desktop only) + if (isTauriDesktopEnv) { + setError(null); + setOauthProvider("Apple"); + setSignUpMethod("paste-code"); + } } else { // Web flow - use AppleAuthProvider component which will initiate the flow console.log("[OAuth] Using web flow for Apple Sign In (Web only)"); From fef1e1937889a84f786b4a0661c4421649ff62d3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:18:12 +0000 Subject: [PATCH 08/10] Fix: use 'Complete Sign Up' instead of 'Complete Login' on signup paste-code screen Co-Authored-By: unknown <> --- frontend/src/routes/signup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index c5d16404..3b83593a 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -557,7 +557,7 @@ function SignupPage() { Verifying... ) : ( - "Complete Login" + "Complete Sign Up" )} From 0b2bd27ed9906311cd8185fedfb6715040d3cc5f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:24:37 +0000 Subject: [PATCH 09/10] Fix: update stale instruction text to match removed paste button Co-Authored-By: unknown <> --- frontend/src/routes/auth.$provider.callback.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/auth.$provider.callback.tsx b/frontend/src/routes/auth.$provider.callback.tsx index fe298aa0..d0d998e5 100644 --- a/frontend/src/routes/auth.$provider.callback.tsx +++ b/frontend/src/routes/auth.$provider.callback.tsx @@ -223,7 +223,7 @@ function OAuthCallback() {

- In the Maple app, tap "Paste Login Code" on the login screen. + Switch to the Maple app and paste the code into the login code input.

)} From f3db5fab3a81eebae750d3fbe6032fe8c4b9f10f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 03:12:49 +0000 Subject: [PATCH 10/10] Enable paste-code fallback for all Tauri platforms (desktop + iOS/Android) Co-Authored-By: unknown <> --- frontend/src/routes/login.tsx | 33 +++++++++++++-------------------- frontend/src/routes/signup.tsx | 33 +++++++++++++-------------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx index de0d89a3..f92ec62f 100644 --- a/frontend/src/routes/login.tsx +++ b/frontend/src/routes/login.tsx @@ -16,7 +16,7 @@ import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; import { AppleAuthProvider } from "@/components/AppleAuthProvider"; import { getBillingService } from "@/billing/billingService"; -import { isIOS, isTauri, isTauriDesktop } from "@/utils/platform"; +import { isIOS, isTauri } from "@/utils/platform"; type LoginSearchParams = { next?: string; @@ -50,7 +50,6 @@ function LoginPage() { // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); - const isTauriDesktopEnv = isTauriDesktop(); // Show paste code input after a delay when auto-navigated from OAuth useEffect(() => { @@ -191,12 +190,10 @@ function LoginPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open (desktop only) - if (isTauriDesktopEnv) { - setError(null); - setOauthProvider("GitHub"); - setLoginMethod("paste-code"); - } + // Navigate to paste-code screen so user sees it while browser is open + setError(null); + setOauthProvider("GitHub"); + setLoginMethod("paste-code"); } else { // Web flow remains unchanged const { auth_url } = await os.initiateGitHubAuth(""); @@ -240,12 +237,10 @@ function LoginPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open (desktop only) - if (isTauriDesktopEnv) { - setError(null); - setOauthProvider("Google"); - setLoginMethod("paste-code"); - } + // Navigate to paste-code screen so user sees it while browser is open + setError(null); + setOauthProvider("Google"); + setLoginMethod("paste-code"); } else { // Web flow remains unchanged const { auth_url } = await os.initiateGoogleAuth(""); @@ -411,12 +406,10 @@ function LoginPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open (desktop only) - if (isTauriDesktopEnv) { - setError(null); - setOauthProvider("Apple"); - setLoginMethod("paste-code"); - } + // Navigate to paste-code screen so user sees it while browser is open + setError(null); + setOauthProvider("Apple"); + setLoginMethod("paste-code"); } else { // Web flow - use AppleAuthProvider component which will initiate the flow console.log("[OAuth] Using web flow for Apple Sign In (Web only)"); diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx index 3b83593a..140340e3 100644 --- a/frontend/src/routes/signup.tsx +++ b/frontend/src/routes/signup.tsx @@ -16,7 +16,7 @@ import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; import { AppleAuthProvider } from "@/components/AppleAuthProvider"; import { getBillingService } from "@/billing/billingService"; -import { isIOS, isTauri, isTauriDesktop } from "@/utils/platform"; +import { isIOS, isTauri } from "@/utils/platform"; import { GuestSignupWarningDialog } from "@/components/GuestSignupWarningDialog"; import { GuestCredentialsDialog } from "@/components/GuestCredentialsDialog"; import { UserCircle } from "lucide-react"; @@ -56,7 +56,6 @@ function SignupPage() { // Use platform detection functions const isIOSPlatform = isIOS(); const isTauriEnv = isTauri(); - const isTauriDesktopEnv = isTauriDesktop(); // Show paste code input after a delay when auto-navigated from OAuth useEffect(() => { @@ -202,12 +201,10 @@ function SignupPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open (desktop only) - if (isTauriDesktopEnv) { - setError(null); - setOauthProvider("GitHub"); - setSignUpMethod("paste-code"); - } + // Navigate to paste-code screen so user sees it while browser is open + setError(null); + setOauthProvider("GitHub"); + setSignUpMethod("paste-code"); } else { // Web flow remains unchanged const { auth_url } = await os.initiateGitHubAuth(""); @@ -251,12 +248,10 @@ function SignupPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open (desktop only) - if (isTauriDesktopEnv) { - setError(null); - setOauthProvider("Google"); - setSignUpMethod("paste-code"); - } + // Navigate to paste-code screen so user sees it while browser is open + setError(null); + setOauthProvider("Google"); + setSignUpMethod("paste-code"); } else { // Web flow remains unchanged const { auth_url } = await os.initiateGoogleAuth(""); @@ -427,12 +422,10 @@ function SignupPage() { setError("Failed to open authentication page in browser"); }); - // Navigate to paste-code screen so user sees it while browser is open (desktop only) - if (isTauriDesktopEnv) { - setError(null); - setOauthProvider("Apple"); - setSignUpMethod("paste-code"); - } + // Navigate to paste-code screen so user sees it while browser is open + setError(null); + setOauthProvider("Apple"); + setSignUpMethod("paste-code"); } else { // Web flow - use AppleAuthProvider component which will initiate the flow console.log("[OAuth] Using web flow for Apple Sign In (Web only)");