Post by Derek Ross
There's a .cursorrules file.
1
0
# Project Overview This project is a Nostr client application built with React 18.x, TailwindCSS 3.x, Vite, shadcn/ui, and Nostrify. ## Technology Stack - **React 18.x**: Stable version of React with hooks, concurrent rendering, and improved performance - **TailwindCSS 3.x**: Utility-first CSS framework for styling - **Vite**: Fast build tool and development server - **shadcn/ui**: Unstyled, accessible UI components built with Radix UI and Tailwind - **Nostrify**: Nostr protocol framework for Deno and web - **React Router**: For client-side routing - **TanStack Query**: For data fetching, caching, and state management - **TypeScript**: For type-safe JavaScript development ## Project Structure - `/src/components/`: UI components including NostrProvider for Nostr integration - `/src/hooks/`: Custom hooks including `useNostr` and `useNostrQuery` - `/src/pages/`: Page components used by React Router - `/src/lib/`: Utility functions and shared logic - `/public/`: Static assets ## UI Components The project uses shadcn/ui components located in `@/components/ui`. These are unstyled, accessible components built with Radix UI and styled with Tailwind CSS. Available components include: - **Accordion**: Vertically collapsing content panels - **Alert**: Displays important messages to users - **AlertDialog**: Modal dialog for critical actions requiring confirmation - **AspectRatio**: Maintains consistent width-to-height ratio - **Avatar**: User profile pictures with fallback support - **Badge**: Small status descriptors for UI elements - **Breadcrumb**: Navigation aid showing current location in hierarchy - **Button**: Customizable button with multiple variants and sizes - **Calendar**: Date picker component - **Card**: Container with header, content, and footer sections - **Carousel**: Slideshow for cycling through elements - **Chart**: Data visualization component - **Checkbox**: Selectable input element - **Collapsible**: Toggle for showing/hiding content - **Command**: Command palette for keyboard-first interfaces - **ContextMenu**: Right-click menu component - **Dialog**: Modal window overlay - **Drawer**: Side-sliding panel - **DropdownMenu**: Menu that appears from a trigger element - **Form**: Form validation and submission handling - **HoverCard**: Card that appears when hovering over an element - **InputOTP**: One-time password input field - **Input**: Text input field - **Label**: Accessible form labels - **Menubar**: Horizontal menu with dropdowns - **NavigationMenu**: Accessible navigation component - **Pagination**: Controls for navigating between pages - **Popover**: Floating content triggered by a button - **Progress**: Progress indicator - **RadioGroup**: Group of radio inputs - **Resizable**: Resizable panels and interfaces - **ScrollArea**: Scrollable container with custom scrollbars - **Select**: Dropdown selection component - **Separator**: Visual divider between content - **Sheet**: Side-anchored dialog component - **Sidebar**: Navigation sidebar component - **Skeleton**: Loading placeholder - **Slider**: Input for selecting a value from a range - **Sonner**: Toast notification manager - **Switch**: Toggle switch control - **Table**: Data table with headers and rows - **Tabs**: Tabbed interface component - **Textarea**: Multi-line text input - **Toast**: Toast notification component - **ToggleGroup**: Group of toggle buttons - **Toggle**: Two-state button - **Tooltip**: Informational text that appears on hover These components follow a consistent pattern using React's `forwardRef` and use the `cn()` utility for class name merging. Many are built on Radix UI primitives for accessibility and customized with Tailwind CSS. ## Nostr Protocol Integration This project comes with custom hooks for querying and publishing events on the Nostr network. ### The `useNostr` Hook The `useNostr` hook returns an object containing a `nostr` property, with `.query()` and `.event()` methods for querying and publishing Nostr events respectively. ```typescript import { useNostr } from '@nostrify/react'; function useCustomHook() { const { nostr } = useNostr(); // ... } ``` ### Query Nostr Data with `useNostr` and Tanstack Query When querying Nostr, the best practice is to create custom hooks that combine `useNostr` and `useQuery` to get the required data. ```typescript import { useNostr } from '@nostrify/react'; import { useQuery } from '@tanstack/query'; function usePosts() { const { nostr } = useNostr(); return useQuery({ queryKey: ['posts'], queryFn: async (c) => { const signal = AbortSignal.any([c.signal, AbortSignal.timeout(1500)]); const events = await nostr.query([{ kinds: [1], limit: 20 }], { signal }); return events; // these events could be transformed into another format }, }); } ``` The data may be transformed into a more appropriate format if needed, and multiple calls to `nostr.query()` may be made in a single queryFn. ### The `useAuthor` Hook To display profile data for a user by their Nostr pubkey (such as an event author), use the `useAuthor` hook. ```tsx import { NostrEvent, NostrMetadata } from '@nostrify/nostrify'; import { useAuthor } from '@/hooks/useAuthor'; function Post({ event }: { event: NostrEvent }) { const author = useAuthor(event.pubkey); const metadata: NostrMetadata | undefined = author.data?.metadata; const displayName = metadata?.name || event.pubkey.slice(0, 8); const profileImage = metadata?.picture; // ...render elements with this data } ``` #### `NostrMetadata` type ```ts /** Kind 0 metadata. */ interface NostrMetadata { /** A short description of the user. */ about?: string; /** A URL to a wide (~1024x768) picture to be optionally displayed in the background of a profile screen. */ banner?: string; /** A boolean to clarify that the content is entirely or partially the result of automation, such as with chatbots or newsfeeds. */ bot?: boolean; /** An alternative, bigger name with richer characters than `name`. `name` should always be set regardless of the presence of `display_name` in the metadata. */ display_name?: string; /** A bech32 lightning address according to NIP-57 and LNURL specifications. */ lud06?: string; /** An email-like lightning address according to NIP-57 and LNURL specifications. */ lud16?: string; /** A short name to be displayed for the user. */ name?: string; /** An email-like Nostr address according to NIP-05. */ nip05?: string; /** A URL to the user's avatar. */ picture?: string; /** A web URL related in any way to the event author. */ website?: string; } ``` ### The `useNostrPublish` Hook To publish events, use the `useNostrPublish` hook in this project. ```tsx import { useState } from 'react'; import { useCurrentUser } from "@/hooks/useCurrentUser"; import { useNostrPublish } from '@/hooks/useNostrPublish'; export function MyComponent() { const [ data, setData] = useState<Record<string, string>>({}); const { user } = useCurrentUser(); const { mutate: createEvent } = useNostrPublish(); const handleSubmit = () => { createEvent({ kind: 1, content: data.content }); }; if (!user) { return <span>You must be logged in to use this form.</span>; } return ( <form onSubmit={handleSubmit} disabled={!user}> {/* ...some input fields */} </form> ); } ``` The `useCurrentUser` hook should be used to ensure that the user is logged in before they are able to publish Nostr events. ### Nostr Login To enable login with Nostr, simply use the `LoginArea` component already included in this project. ```tsx import { LoginArea } from "@/components/auth/LoginArea"; function MyComponent() { return ( <div> {/* other components ... */} <LoginArea /> </div> ); } ``` The `LoginArea` component displays a "Log in" button when the user is logged out, and changes to an account switcher once the user is logged in. It handles all the login-related UI and interactions internally, including displaying login dialogs and switching between accounts. ## `npub`, `naddr`, and other Nostr addresses Nostr defines a set identifiers in NIP-19. Their prefixes: - `npub`: public keys - `nsec`: private keys - `note`: note ids - `nprofile`: a nostr profile - `nevent`: a nostr event - `naddr`: a nostr replaceable event coordinate - `nrelay`: a nostr relay (deprecated) NIP-19 identifiers include a prefix, the number "1", then a base32-encoded data string. ### Use in Filters The base Nostr protocol uses hex string identifiers for filtering by event IDs, pubkeys, and signatures. Nostr filters only accept hex strings. ```ts // ❌ Wrong: naddr is not decoded const events = await nostr.query( [{ ids: [naddr] }], { signal } ); ``` Corrected example: ```ts // Import nip19 from nostr-tools import { nip19 } from 'nostr-tools'; // Decode a NIP-19 identifier const decoded = nip19.decode(value); // Optional: guard certain types if (decoded.type !== 'naddr') { throw new Error('Invalid stack ID'); } // Get the addr object const naddr = decoded.data; // βœ… Correct: naddr is expanded into the correct filter const events = await nostr.query( [{ kinds: [naddr.kind], authors: [naddr.pubkey], '#d': [naddr.identifier], }], { signal } ); ``` ## Development Practices - Uses React Query for data fetching and caching - Follows shadcn/ui component patterns - Implements Path Aliases with `@/` prefix for cleaner imports - Uses Vite for fast development and production builds - Component-based architecture with React hooks - Default connection to multiple Nostr relays for network redundancy ## Build & Deployment - Build for production: `npm run build` - Development build: `npm run build:dev` ## Testing Your Changes Whenever you modify code, you should test your changes after you're finished by running: ```bash npm run ci ``` This command will typecheck the code and attempt to build it. Your task is not considered finished until this test passes without errors.
2
0
1
0
Shh. Don't tell him my secrets.
1
21
He has many more secrets, and I want him to share them!
0
0
Amethyst really punishes you for clicking on 'Show More' sometimes lol
1
8
I hope you learned something πŸ₯Ή
0
0