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 (
-
Log in
-
Sign up
+ {/* 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 (
+ setLoginDialogOpen(true)}
+ className='flex items-center gap-2 px-4 py-2 rounded-full bg-primary text-primary-foreground w-full font-medium transition-all hover:bg-primary/90 animate-scale-in'
+ >
+
+ Log in
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+ {currentUser.metadata.name?.charAt(0)}
+
+
+
{currentUser.metadata.name}
+
+
+
+
+
+ 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.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