>({});
const { user } = useCurrentUser();
const { mutate: createEvent } = useNostrPublish();
const handleSubmit = () => {
createEvent({ kind: 1, content: data.content });
};
if (!user) {
return You must be logged in to use this form.;
}
return (
);
}
```
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 (
{/* other components ... */}
);
}
```
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. It should not be wrapped in any conditional logic.
### `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 when filtering by event IDs and pubkeys. 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 (depending on the use-case)
if (decoded.type !== 'naddr') {
throw new Error('Unsupported Nostr identifier');
}
// 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 }
);
```
### Nostr Edit Profile
To include an Edit Profile form, place the `EditProfileForm` component in the project:
```tsx
import { EditProfileForm } from "@/components/EditProfileForm";
function EditProfilePage() {
return (
{/* you may want to wrap this in a layout or include other components depending on the project ... */}
);
}
```
The `EditProfileForm` component displays just the form. It requires no props, and will "just work" automatically.
### Uploading Files on Nostr
Use the `useUploadFile` hook to upload files.
```tsx
import { useUploadFile } from "@/hooks/useUploadFile";
function MyComponent() {
const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile();
const handleUpload = async (file: File) => {
try {
// Provides an array of NIP-94 compatible tags
// The first tag in the array contains the URL
const [[_, url]] = await uploadFile(file);
// ...use the url
} catch (error) {
// ...handle errors
}
};
// ...rest of component
}
```
To attach files to kind 1 events, each file's URL should be appended to the event's `content`, and an `imeta` tag should be added for each file. For kind 0 events, the URL by itself can be used in relevant fields of the JSON content.
### Nostr Encryption and Decryption
The logged-in user has a `signer` object (matching the NIP-07 signer interface) that can be used for encryption and decryption.
```ts
// Get the current user
const { user } = useCurrentUser();
// Optional guard to check that nip44 is available
if (!user.signer.nip44) {
throw new Error("Please upgrade your signer extension to a version that supports NIP-44 encryption");
}
// Encrypt message to self
const encrypted = await user.signer.nip44.encrypt(user.pubkey, "hello world");
// Decrypt message to self
const decrypted = await user.signer.nip44.decrypt(user.pubkey, encrypted) // "hello world"
```
### Rendering Kind 1 Text
If you need to render kind 1 text, use the `NoteContent` component:
```tsx
import { NoteContent } from "@/components/NoteContent";
export function Post(/* ...props */) {
// ...
return (
);
}
```
## 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 test
```
This command will typecheck the code and attempt to build it.
Your task is not considered finished until this test passes without errors.