- puterProxyApi: storeToken, getAuthUrl, claimGoogleTokens, storeApiKey - PuterAuthButton: loads Puter CDN script, triggers signIn popup, captures token - GoogleOAuthButton: 3-second risk warning gate, opens OAuth popup, captures stateId - ApiKeyEntryForm: provider dropdown (OpenAI/Anthropic/Groq) + password input
105 lines
2.9 KiB
TypeScript
105 lines
2.9 KiB
TypeScript
// [nexus] Puter auth button — loads CDN script, triggers signIn popup, captures token
|
|
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { CheckCircle, LogIn } from "lucide-react";
|
|
|
|
interface PuterAuthButtonProps {
|
|
onSuccess: (token: string) => void;
|
|
onError: (msg: string) => void;
|
|
}
|
|
|
|
function loadScript(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
if ((window as any).puter) {
|
|
resolve();
|
|
return;
|
|
}
|
|
const script = document.createElement("script");
|
|
script.src = "https://js.puter.com/v2/";
|
|
script.onload = () => resolve();
|
|
script.onerror = () => reject(new Error("Failed to load Puter SDK"));
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
export function PuterAuthButton({ onSuccess, onError }: PuterAuthButtonProps) {
|
|
const [loading, setLoading] = useState(false);
|
|
const [connected, setConnected] = useState(false);
|
|
|
|
async function handleClick() {
|
|
setLoading(true);
|
|
try {
|
|
await loadScript();
|
|
|
|
// signIn must be called in the same async chain as the click event for popup to work
|
|
await (window as any).puter.auth.signIn();
|
|
|
|
// Extract token — try multiple access patterns (Pitfall 1: token location varies)
|
|
let token: string | undefined =
|
|
(window as any).puter?.authToken ??
|
|
(window as any).puter?.auth?.token;
|
|
|
|
if (!token) {
|
|
const user = await (window as any).puter?.auth?.getUser?.();
|
|
token = user?.token;
|
|
}
|
|
|
|
if (!token) {
|
|
console.warn("[nexus] Puter token is undefined after signIn — user may not be authenticated");
|
|
}
|
|
|
|
onSuccess(token ?? "");
|
|
setConnected(true);
|
|
} catch {
|
|
onError("Puter sign-in failed. Check your Puter.com account and try again.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Button
|
|
type="button"
|
|
onClick={handleClick}
|
|
disabled={loading || connected}
|
|
aria-busy={loading}
|
|
className="w-full"
|
|
>
|
|
{loading ? (
|
|
<span className="flex items-center gap-2">
|
|
<svg
|
|
className="h-4 w-4 animate-spin"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
aria-hidden="true"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
/>
|
|
</svg>
|
|
Connecting to Puter...
|
|
</span>
|
|
) : connected ? (
|
|
<span className="flex items-center gap-2">
|
|
<CheckCircle className="h-4 w-4" />
|
|
Puter connected
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center gap-2">
|
|
<LogIn className="h-4 w-4" />
|
|
Continue with Puter
|
|
</span>
|
|
)}
|
|
</Button>
|
|
);
|
|
}
|