// src/components/Login.tsx import React, { useEffect, useState, useRef } from "react"; import { useAuth } from "~/providers/UnifiedAuthProvider"; import { Agent } from "@atproto/api"; // --- 1. The Main Component (Orchestrator with `compact` prop) --- export default function Login({ compact = false }: { compact?: boolean }) { const { status, agent, logout } = useAuth(); // Loading state can be styled differently based on the prop if (status === "loading") { return (
); } // --- LOGGED IN STATE --- if (status === "signedIn") { // Large view if (!compact) { return (

You are logged in!

); } // Compact view return (
); } // --- LOGGED OUT STATE --- if (!compact) { // Large view renders the form directly in the card return (
); } // Compact view renders a button that toggles the form in a dropdown return ; } // --- 2. The Reusable, Self-Contained Login Form Component --- export function UnifiedLoginForm() { const [mode, setMode] = useState<"oauth" | "password">("oauth"); return (
setMode("oauth")} /> setMode("password")} />
{mode === "oauth" ? : }
); } // --- 3. Helper components for layouts, forms, and UI --- // A new component to contain the logic for the compact dropdown const CompactLoginButton = () => { const [showForm, setShowForm] = useState(false); const formRef = useRef(null); useEffect(() => { function handleClickOutside(event: MouseEvent) { if (formRef.current && !formRef.current.contains(event.target as Node)) { setShowForm(false); } } if (showForm) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [showForm]); return (
{showForm && (
)}
); }; const TabButton = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void; }) => ( ); const OAuthForm = () => { const { loginWithOAuth } = useAuth(); const [handle, setHandle] = useState(""); useEffect(() => { const lastHandle = localStorage.getItem("lastHandle"); if (lastHandle) setHandle(lastHandle); }, []); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (handle.trim()) { localStorage.setItem("lastHandle", handle); loginWithOAuth(handle); } }; return (

Sign in with AT. Your password is never shared.

setHandle(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" />
); }; const PasswordForm = () => { const { loginWithPassword } = useAuth(); const [user, setUser] = useState(""); const [password, setPassword] = useState(""); const [serviceURL, setServiceURL] = useState("bsky.social"); const [error, setError] = useState(null); useEffect(() => { const lastHandle = localStorage.getItem("lastHandle"); if (lastHandle) setUser(lastHandle); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); try { localStorage.setItem("lastHandle", user); await loginWithPassword(user, password, `https://${serviceURL}`); } catch (err) { setError("Login failed. Check your handle and App Password."); } }; return (

Warning: Less secure. Use an App Password.

setUser(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="username" /> setPassword(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="current-password" /> setServiceURL(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" /> {error &&

{error}

}
); }; // --- Profile Component (now supports a `large` prop for styling) --- export const ProfileThing = ({ agent, large = false }: { agent: Agent | null; large?: boolean }) => { const [profile, setProfile] = useState(null); useEffect(() => { const fetchUser = async () => { const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid; if (!did) return; try { const res = await agent!.getProfile({ actor: did }); setProfile(res.data); } catch (e) { console.error("Failed to fetch profile", e); } }; if (agent) fetchUser(); }, [agent]); if (!profile) { return ( // Skeleton loader
); } return (
avatar
{profile?.displayName}
@{profile?.handle}
); };