an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 78b79fd1c22ab89932161b28fab22f85a3d9a334 227 lines 8.2 kB view raw
1import React, { useEffect, useState, useRef } from "react"; 2import { useAuth } from "~/providers/PassAuthProvider"; 3 4interface LoginProps { 5 compact?: boolean; 6} 7 8export default function Login({ compact = false }: LoginProps) { 9 const { loginStatus, login, logout, loading, authed } = useAuth(); 10 const [user, setUser] = useState(""); 11 const [password, setPassword] = useState(""); 12 const [serviceURL, setServiceURL] = useState("bsky.social"); 13 const [showLoginForm, setShowLoginForm] = useState(false); 14 const formRef = useRef<HTMLDivElement>(null); 15 16 useEffect(() => { 17 function handleClickOutside(event: MouseEvent) { 18 if (formRef.current && !formRef.current.contains(event.target as Node)) { 19 setShowLoginForm(false); 20 } 21 } 22 23 if (showLoginForm) { 24 document.addEventListener("mousedown", handleClickOutside); 25 } 26 27 return () => { 28 document.removeEventListener("mousedown", handleClickOutside); 29 }; 30 }, [showLoginForm]); 31 32 if (loading) { 33 return ( 34 <div className="flex items-center justify-center p-6 text-gray-500 dark:text-gray-400"> 35 Loading... 36 </div> 37 ); 38 } 39 40 if (compact) { 41 if (authed) { 42 return ( 43 <button 44 onClick={logout} 45 className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded px-3 py-1 font-medium transition-colors" 46 > 47 Log out 48 </button> 49 ); 50 } else { 51 return ( 52 <div className="relative" ref={formRef}> 53 <button 54 onClick={() => setShowLoginForm(!showLoginForm)} 55 className="text-sm bg-gray-600 hover:bg-gray-700 text-white rounded px-3 py-1 font-medium transition-colors" 56 > 57 Log in 58 </button> 59 {showLoginForm && ( 60 <div className="absolute top-full right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50"> 61 <form 62 onSubmit={(e) => { 63 e.preventDefault(); 64 login(user, password, `https://${serviceURL}`); 65 setShowLoginForm(false); 66 }} 67 className="flex flex-col gap-3" 68 > 69 <p className="text-xs text-gray-500 dark:text-gray-400"> 70 sorry for the temporary login, 71 <br /> 72 oauth will come soon enough i swear 73 </p> 74 <input 75 type="text" 76 placeholder="Username" 77 value={user} 78 onChange={(e) => setUser(e.target.value)} 79 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-blue-500" 80 autoComplete="username" 81 /> 82 <input 83 type="password" 84 placeholder="Password" 85 value={password} 86 onChange={(e) => setPassword(e.target.value)} 87 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-blue-500" 88 autoComplete="current-password" 89 /> 90 <input 91 type="text" 92 placeholder="bsky.social" 93 value={serviceURL} 94 onChange={(e) => setServiceURL(e.target.value)} 95 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-blue-500" 96 /> 97 <button 98 type="submit" 99 className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors" 100 > 101 Log in 102 </button> 103 </form> 104 </div> 105 )} 106 </div> 107 ); 108 } 109 } 110 111 return ( 112 <div className="p-6 bg-gray-100 dark:bg-gray-900 rounded-xl shadow border border-gray-200 dark:border-gray-800 mt-6 mx-4"> 113 {authed ? ( 114 <div className="flex flex-col items-center justify-center text-center"> 115 <p className="text-lg font-semibold mb-6 text-gray-800 dark:text-gray-100"> 116 You are logged in! 117 </p> 118 <button 119 onClick={logout} 120 className="bg-gray-600 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors" 121 > 122 Log out 123 </button> 124 </div> 125 ) : ( 126 <form 127 onSubmit={(e) => { 128 e.preventDefault(); 129 login(user, password, `https://${serviceURL}`); 130 }} 131 className="flex flex-col gap-4" 132 > 133 <p className="text-sm text-gray-500 dark:text-gray-400 mb-2"> 134 sorry for the temporary login, 135 <br /> 136 oauth will come soon enough i swear 137 </p> 138 <input 139 type="text" 140 placeholder="Username" 141 value={user} 142 onChange={(e) => setUser(e.target.value)} 143 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-base focus:outline-none focus:ring-2 focus:ring-blue-500" 144 autoComplete="username" 145 /> 146 <input 147 type="password" 148 placeholder="Password" 149 value={password} 150 onChange={(e) => setPassword(e.target.value)} 151 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-base focus:outline-none focus:ring-2 focus:ring-blue-500" 152 autoComplete="current-password" 153 /> 154 <input 155 type="text" 156 placeholder="bsky.social" 157 value={serviceURL} 158 onChange={(e) => setServiceURL(e.target.value)} 159 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-base focus:outline-none focus:ring-2 focus:ring-blue-500" 160 /> 161 <button 162 type="submit" 163 className="bg-gray-600 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors mt-2" 164 > 165 Log in 166 </button> 167 </form> 168 )} 169 </div> 170 ); 171} 172 173export const ProfileThing = () => { 174 const { agent, loading, loginStatus, authed } = useAuth(); 175 const [response, setResponse] = useState<any>(null); 176 177 useEffect(() => { 178 if (loginStatus && agent && !loading && authed) { 179 fetchUser(); 180 } 181 // eslint-disable-next-line 182 }, [loginStatus, agent, loading, authed]); 183 184 const fetchUser = async () => { 185 if (!agent) { 186 console.error("Agent is null or undefined"); 187 return; 188 } 189 const res = await agent.app.bsky.actor.getProfile({ 190 actor: agent.assertDid, 191 }); 192 setResponse(res.data); 193 }; 194 195 if (!authed) { 196 return ( 197 <div className="inline-block"> 198 <span className="text-gray-100 text-base font-medium px-1.5"> 199 Login 200 </span> 201 </div> 202 ); 203 } 204 205 if (!response) { 206 return ( 207 <div className="flex flex-col items-start gap-1.5"> 208 <span className="w-5 h-5 border-2 border-gray-200 dark:border-gray-600 border-t-transparent rounded-full animate-spin inline-block" /> 209 <span className="text-gray-100">Loading... </span> 210 </div> 211 ); 212 } 213 214 return ( 215 <div className="flex flex-row items-start gap-1.5"> 216 <img 217 src={response?.avatar} 218 alt="avatar" 219 className="w-[30px] h-[30px] rounded-full object-cover" 220 /> 221 <div> 222 <div className="text-gray-100 text-xs">{response?.displayName}</div> 223 <div className="text-gray-100 text-xs">@{response?.handle}</div> 224 </div> 225 </div> 226 ); 227};