From 360b9062b826a4fe3e59df6b8a34ef13a5d2f4a6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 17 Apr 2025 23:27:15 -0500 Subject: [PATCH] Include the AccountSwitcher component --- CONTEXT.md | 41 +------- src/components/auth/AccountSwitcher.tsx | 126 ++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 36 deletions(-) create mode 100644 src/components/auth/AccountSwitcher.tsx diff --git a/CONTEXT.md b/CONTEXT.md index b9d6b82..3d0cb73 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -152,54 +152,23 @@ The `useCurrentUser` hook should be used to ensure that the user is logged in be ### Nostr Login -To add Nostr login functionality, use the included `LoginForm` and `SignupForm` dialog components. For example: +To enable login with Nostr, simply include the `AccountSwitcher` component already included in this project. ```tsx -import LoginForm from "@/components/auth/LoginForm"; -import SignupForm from "@/components/auth/SignupForm"; -import { Button } from "@/components/ui/button"; +import { AccountSwitcher } from "@/components/auth/AccountSwitcher"; function MyComponent() { - const [loginDialogOpen, setLoginDialogOpen] = useState(false); - const [signupDialogOpen, setSignupDialogOpen] = useState(false); - - const handleLogin = () => { - setLoginDialogOpen(false); - setSignupDialogOpen(false); - }; - return (
- - + {/* other components ... */} - setLoginDialogOpen(false)} - onLogin={handleLogin} - onSignup={showSignupDialog} - /> - - setSignupDialogOpen(false)} - /> +
); } ``` -To access the currently-logged-in account, use the `useCurrentUser` hook, eg: - -```typescript -import { useCurrentUser } from "@/hooks/useCurrentUser"; - -function MyComponent() { - const { user } = useCurrentUser(); - - // ... -} -``` +The `AccountSwitcher` component displays a "Log in" button when the user is logged out, and changes to an account switcher once the user is logged in. ## Development Practices diff --git a/src/components/auth/AccountSwitcher.tsx b/src/components/auth/AccountSwitcher.tsx new file mode 100644 index 0000000..94ce698 --- /dev/null +++ b/src/components/auth/AccountSwitcher.tsx @@ -0,0 +1,126 @@ +import { useState } from 'react'; +import { ChevronDown, LogOut, User, UserPlus } from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu.tsx'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.tsx'; +import { Button } from '@/components/ui/button.tsx'; +import { useNostrLogin } from '@nostrify/react/login'; +import { useQuery } from '@tanstack/react-query'; +import { useNostr } from '@nostrify/react'; +import { NSchema as n, NostrEvent, NostrMetadata } from '@nostrify/nostrify'; +import LoginForm from './LoginForm'; +import SignupForm from './SignupForm'; + +export function AccountSwitcher() { + const { nostr } = useNostr(); + const { logins, setLogin, removeLogin } = useNostrLogin(); + + const [loginDialogOpen, setLoginDialogOpen] = useState(false); + const [signupDialogOpen, setSignupDialogOpen] = useState(false); + + const { data: authors } = useQuery({ + queryKey: ['logins', logins], + queryFn: async () => { + const events = await nostr.query([{ kinds: [0], authors: logins.map((l) => l.pubkey) }]); + return logins.map(({ id, pubkey }): { id: string; pubkey: string; event?: NostrEvent; metadata: NostrMetadata } => { + const event = events.find((e) => e.pubkey === pubkey); + try { + const metadata = n.json().pipe(n.metadata()).parse(event?.content); + return { id, pubkey, metadata, event }; + } catch { + return { id, pubkey, metadata: {}, event }; + } + }); + } + }); + + const [currentUser, ...otherUsers] = authors || []; + const isLoggedIn = !!currentUser; + + const handleLogin = () => { + setLoginDialogOpen(false); + setSignupDialogOpen(false); + }; + + if (!isLoggedIn) { + return ( + + ); + } + + return ( + <> + + + + + +
Switch Account
+ {otherUsers.map((user) => ( + setLogin(user.id)} + className='flex items-center gap-2 cursor-pointer p-2 rounded-md' + > + + + {user.metadata.name?.charAt(0)} + +
+

{user.metadata.name}

+
+ {user.id === currentUser.id &&
} +
+ ))} + + setLoginDialogOpen(true)} + className='flex items-center gap-2 cursor-pointer p-2 rounded-md' + > + + Add another account + + removeLogin(currentUser.id)} + className='flex items-center gap-2 cursor-pointer p-2 rounded-md text-red-500' + > + + Log out + +
+
+ + setLoginDialogOpen(false)} + onLogin={handleLogin} + onSignup={() => setSignupDialogOpen(true)} + /> + + setSignupDialogOpen(false)} + /> + + ); +} \ No newline at end of file