this repo has no description
at filter-styling 123 lines 5.0 kB view raw
1import { Client, CredentialManager } from "@atcute/client"; 2import { Did } from "@atcute/lexicons"; 3import { deleteStoredSession, getSession, OAuthUserAgent } from "@atcute/oauth-browser-client"; 4import { A } from "@solidjs/router"; 5import { createSignal, For, onMount, Show } from "solid-js"; 6import { createStore } from "solid-js/store"; 7import { resolveDidDoc } from "../utils/api.js"; 8import { agent, Login, retrieveSession, setAgent } from "./login.jsx"; 9import { Modal } from "./modal.jsx"; 10 11const AccountManager = () => { 12 const [openManager, setOpenManager] = createSignal(false); 13 const [sessions, setSessions] = createStore<Record<string, string | undefined>>(); 14 const [avatar, setAvatar] = createSignal<string>(); 15 16 onMount(async () => { 17 await retrieveSession(); 18 19 const storedSessions = localStorage.getItem("atcute-oauth:sessions"); 20 if (storedSessions) { 21 const sessionDids = Object.keys(JSON.parse(storedSessions)) as Did[]; 22 sessionDids.forEach((did) => setSessions(did, "")); 23 sessionDids.forEach(async (did) => { 24 const doc = await resolveDidDoc(did); 25 doc.alsoKnownAs?.forEach((alias) => { 26 if (alias.startsWith("at://")) { 27 setSessions(did, alias.replace("at://", "")); 28 return; 29 } 30 }); 31 }); 32 } 33 34 const repo = localStorage.getItem("lastSignedIn"); 35 if (repo) setAvatar(await getAvatar(repo as Did)); 36 }); 37 38 const resumeSession = async (did: Did) => { 39 localStorage.setItem("lastSignedIn", did); 40 retrieveSession(); 41 setAvatar(await getAvatar(did)); 42 }; 43 44 const removeSession = async (did: Did) => { 45 const currentSession = agent()?.sub; 46 try { 47 const session = await getSession(did, { allowStale: true }); 48 const agent = new OAuthUserAgent(session); 49 await agent.signOut(); 50 } catch { 51 deleteStoredSession(did); 52 } 53 setSessions(did, undefined); 54 if (currentSession === did) setAgent(undefined); 55 }; 56 57 const getAvatar = async (did: Did) => { 58 const rpc = new Client({ 59 handler: new CredentialManager({ service: "https://public.api.bsky.app" }), 60 }); 61 const res = await rpc.get("app.bsky.actor.getProfile", { params: { actor: did } }); 62 if (res.ok) { 63 return res.data.avatar; 64 } 65 return undefined; 66 }; 67 68 return ( 69 <> 70 <Modal open={openManager()} onClose={() => setOpenManager(false)}> 71 <div class="dark:bg-dark-800 dark:shadow-dark-800 absolute top-12 left-[50%] w-[22rem] -translate-x-1/2 rounded-lg border-[0.5px] border-neutral-300 bg-neutral-200 p-4 text-neutral-900 shadow-md transition-opacity duration-300 dark:border-neutral-700 dark:text-neutral-200 starting:opacity-0"> 72 <div class="mb-2 flex items-center gap-1 font-semibold"> 73 <span class="iconify lucide--user-round"></span> 74 <span>Manage accounts</span> 75 </div> 76 <div class="mb-3 max-h-[20rem] overflow-y-auto md:max-h-[25rem]"> 77 <For each={Object.keys(sessions)}> 78 {(did) => ( 79 <div class="flex w-full items-center justify-between gap-x-2 rounded-lg hover:bg-neutral-100 active:bg-neutral-100 dark:hover:bg-neutral-600 dark:active:bg-neutral-600"> 80 <button 81 class="flex basis-full items-center justify-between gap-1 truncate p-1" 82 onclick={() => resumeSession(did as Did)} 83 > 84 <span class="truncate">{sessions[did]?.length ? sessions[did] : did}</span> 85 <Show when={did === agent()?.sub}> 86 <span class="iconify lucide--check shrink-0"></span> 87 </Show> 88 </button> 89 <div class="flex items-center gap-1"> 90 <A 91 href={`/at://${did}`} 92 onClick={() => setOpenManager(false)} 93 class="flex items-center p-1" 94 > 95 <span class="iconify lucide--book-user"></span> 96 </A> 97 <button 98 onclick={() => removeSession(did as Did)} 99 class="flex items-center p-1 hover:text-red-500 hover:dark:text-red-400" 100 > 101 <span class="iconify lucide--user-round-x"></span> 102 </button> 103 </div> 104 </div> 105 )} 106 </For> 107 </div> 108 <Login /> 109 </div> 110 </Modal> 111 <button 112 onclick={() => setOpenManager(true)} 113 class="flex items-center rounded-lg p-1 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700" 114 > 115 {agent() && avatar() ? 116 <img src={avatar()} class="dark:shadow-dark-800 size-5 rounded-full shadow-sm" /> 117 : <span class="iconify lucide--circle-user-round text-xl"></span>} 118 </button> 119 </> 120 ); 121}; 122 123export { AccountManager };