tangled
alpha
login
or
join now
whey.party
/
red-dwarf
82
fork
atom
an independent Bluesky client using Constellation, PDS Queries, and other services
reddwarf.app
frontend
spa
bluesky
reddwarf
microcosm
client
app
82
fork
atom
overview
issues
25
pulls
pipelines
constellation profilething
rimar1337
4 months ago
b279cb9c
06ea05da
+23
-22
1 changed file
expand all
collapse all
unified
split
src
components
Login.tsx
+23
-22
src/components/Login.tsx
···
1
// src/components/Login.tsx
2
-
import { Agent } from "@atproto/api";
3
import React, { useEffect, useRef, useState } from "react";
4
5
import { useAuth } from "~/providers/UnifiedAuthProvider";
0
6
7
// --- 1. The Main Component (Orchestrator with `compact` prop) ---
8
export default function Login({
···
110
// --- 3. Helper components for layouts, forms, and UI ---
111
112
// A new component to contain the logic for the compact dropdown
113
-
const CompactLoginButton = ({popup}:{popup?: boolean}) => {
114
const [showForm, setShowForm] = useState(false);
115
const formRef = useRef<HTMLDivElement>(null);
116
···
137
Log in
138
</button>
139
{showForm && (
140
-
<div className={`absolute ${popup ? `bottom-[calc(100%)]` :`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`}>
0
0
141
<UnifiedLoginForm />
142
</div>
143
)}
···
268
269
// --- Profile Component (now supports a `large` prop for styling) ---
270
export const ProfileThing = ({
271
-
agent,
272
large = false,
273
}: {
274
-
agent: Agent | null;
275
large?: boolean;
276
}) => {
277
-
const [profile, setProfile] = useState<any>(null);
0
0
0
0
0
0
278
279
-
useEffect(() => {
280
-
const fetchUser = async () => {
281
-
const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid;
282
-
if (!did) return;
283
-
try {
284
-
const res = await agent!.getProfile({ actor: did });
285
-
setProfile(res.data);
286
-
} catch (e) {
287
-
console.error("Failed to fetch profile", e);
288
-
}
289
-
};
290
-
if (agent) fetchUser();
291
-
}, [agent]);
292
293
-
if (!profile) {
294
return (
295
// Skeleton loader
296
<div
···
316
className={`flex flex-row items-center gap-2.5 ${large ? "mb-1" : ""}`}
317
>
318
<img
319
-
src={profile?.avatar}
320
alt="avatar"
321
className={`object-cover rounded-full ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`}
322
/>
···
329
<div
330
className={` ${large ? "text-gray-500 dark:text-gray-400 text-sm" : "text-gray-500 dark:text-gray-400 text-xs"}`}
331
>
332
-
@{profile?.handle}
333
</div>
334
</div>
335
</div>
···
1
// src/components/Login.tsx
2
+
import AtpAgent, { Agent } from "@atproto/api";
3
import React, { useEffect, useRef, useState } from "react";
4
5
import { useAuth } from "~/providers/UnifiedAuthProvider";
6
+
import { useQueryIdentity, useQueryProfile } from "~/utils/useQuery";
7
8
// --- 1. The Main Component (Orchestrator with `compact` prop) ---
9
export default function Login({
···
111
// --- 3. Helper components for layouts, forms, and UI ---
112
113
// A new component to contain the logic for the compact dropdown
114
+
const CompactLoginButton = ({ popup }: { popup?: boolean }) => {
115
const [showForm, setShowForm] = useState(false);
116
const formRef = useRef<HTMLDivElement>(null);
117
···
138
Log in
139
</button>
140
{showForm && (
141
+
<div
142
+
className={`absolute ${popup ? `bottom-[calc(100%)]` : `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`}
143
+
>
144
<UnifiedLoginForm />
145
</div>
146
)}
···
271
272
// --- Profile Component (now supports a `large` prop for styling) ---
273
export const ProfileThing = ({
274
+
agent: _unused,
275
large = false,
276
}: {
277
+
agent?: Agent | null;
278
large?: boolean;
279
}) => {
280
+
const { agent } = useAuth();
281
+
const did = ((agent as AtpAgent).session?.did ?? (agent as AtpAgent)?.assertDid ?? agent?.did) as
282
+
| string
283
+
| undefined;
284
+
const { data: identity } = useQueryIdentity(did);
285
+
const { data: profiledata } = useQueryProfile(`at://${did}/app.bsky.actor.profile/self`);
286
+
const profile = profiledata?.value;
287
288
+
function getAvatarUrl(p: typeof profile) {
289
+
const link = p?.avatar?.ref?.["$link"];
290
+
if (!link || !did) return null;
291
+
return `https://cdn.bsky.app/img/avatar/plain/${did}/${link}@jpeg`;
292
+
}
0
0
0
0
0
0
0
0
293
294
+
if (!profiledata) {
295
return (
296
// Skeleton loader
297
<div
···
317
className={`flex flex-row items-center gap-2.5 ${large ? "mb-1" : ""}`}
318
>
319
<img
320
+
src={getAvatarUrl(profile) ?? undefined}
321
alt="avatar"
322
className={`object-cover rounded-full ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`}
323
/>
···
330
<div
331
className={` ${large ? "text-gray-500 dark:text-gray-400 text-sm" : "text-gray-500 dark:text-gray-400 text-xs"}`}
332
>
333
+
@{identity?.handle}
334
</div>
335
</div>
336
</div>