ATProto forum built with ESAV
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">sorry for the temporary login,<br />oauth will come soon enough i swear</p>
70 <input
71 type="text"
72 placeholder="Username"
73 value={user}
74 onChange={e => setUser(e.target.value)}
75 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"
76 autoComplete="username"
77 />
78 <input
79 type="password"
80 placeholder="Password"
81 value={password}
82 onChange={e => setPassword(e.target.value)}
83 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"
84 autoComplete="current-password"
85 />
86 <input
87 type="text"
88 placeholder="bsky.social"
89 value={serviceURL}
90 onChange={e => setServiceURL(e.target.value)}
91 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"
92 />
93 <button
94 type="submit"
95 className="bg-gray-600 hover:bg-gray-700 text-white rounded px-4 py-2 font-medium text-sm transition-colors"
96 >
97 Log in
98 </button>
99 </form>
100 </div>
101 )}
102 </div>
103 );
104 }
105 }
106
107 return (
108 <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">
109 {authed ? (
110 <div className="flex flex-col items-center justify-center text-center">
111 <p className="text-lg font-semibold mb-6 text-gray-800 dark:text-gray-100">You are logged in!</p>
112 <button
113 onClick={logout}
114 className="bg-gray-600 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors"
115 >
116 Log out
117 </button>
118 </div>
119 ) : (
120 <form
121 onSubmit={e => {
122 e.preventDefault();
123 login(user, password, `https://${serviceURL}`);
124 }}
125 className="flex flex-col gap-4"
126 >
127 <p className="text-sm text-gray-500 dark:text-gray-400 mb-2">sorry for the temporary login,<br />oauth will come soon enough i swear</p>
128 <input
129 type="text"
130 placeholder="Username"
131 value={user}
132 onChange={e => setUser(e.target.value)}
133 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"
134 autoComplete="username"
135 />
136 <input
137 type="password"
138 placeholder="Password"
139 value={password}
140 onChange={e => setPassword(e.target.value)}
141 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"
142 autoComplete="current-password"
143 />
144 <input
145 type="text"
146 placeholder="bsky.social"
147 value={serviceURL}
148 onChange={e => setServiceURL(e.target.value)}
149 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"
150 />
151 <button
152 type="submit"
153 className="bg-gray-600 hover:bg-gray-700 text-white rounded px-6 py-2 font-semibold text-base transition-colors mt-2"
154 >
155 Log in
156 </button>
157 </form>
158 )}
159 </div>
160 );
161}
162
163export const ProfileThing = () => {
164 const { agent, loading, loginStatus, authed } = useAuth();
165 const [response, setResponse] = useState<any>(null);
166
167 useEffect(() => {
168 if (loginStatus && agent && !loading && authed) {
169 fetchUser();
170 }
171 }, [loginStatus, agent, loading, authed]);
172
173 const fetchUser = async () => {
174 if (!agent) {
175 console.error("Agent is null or undefined");
176 return;
177 }
178 const res = await agent.app.bsky.actor.getProfile({
179 actor: agent.assertDid,
180 });
181 setResponse(res.data);
182 };
183
184 if (!authed) {
185 return (
186 <div className="inline-block">
187 <span className="text-gray-100 text-base font-medium px-1.5">Login</span>
188 </div>
189 );
190 }
191
192 if (!response) {
193 return (
194 <div className="flex flex-col items-start gap-1.5">
195 <span className="w-5 h-5 border-2 border-gray-200 dark:border-gray-600 border-t-transparent rounded-full animate-spin inline-block" />
196 <span className="text-gray-100">Loading... </span>
197 </div>
198 );
199 }
200
201 return (
202 <div className="flex flex-row items-start gap-1.5">
203 <img
204 src={response?.avatar}
205 alt="avatar"
206 className="w-[30px] h-[30px] rounded-full object-cover"
207 />
208 <div>
209 <div className="text-gray-100 text-xs">{response?.displayName}</div>
210 <div className="text-gray-100 text-xs">@{response?.handle}</div>
211 </div>
212 </div>
213 );
214};