diff --git a/CONTEXT.md b/CONTEXT.md
index abeac90..c2bded0 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -272,6 +272,26 @@ const events = await nostr.query(
);
```
+## 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.
+
## Development Practices
- Uses React Query for data fetching and caching
diff --git a/src/components/EditProfileForm.tsx b/src/components/EditProfileForm.tsx
new file mode 100644
index 0000000..39a37bf
--- /dev/null
+++ b/src/components/EditProfileForm.tsx
@@ -0,0 +1,252 @@
+import React, { useEffect } from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useCurrentUser } from '@/hooks/useCurrentUser';
+import { useNostrPublish } from '@/hooks/useNostrPublish';
+import { useToast } from '@/hooks/useToast';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Textarea } from '@/components/ui/textarea';
+import { Switch } from '@/components/ui/switch';
+import { Loader2 } from 'lucide-react';
+import { NSchema as n, type NostrMetadata } from '@nostrify/nostrify';
+import { useQueryClient } from '@tanstack/react-query';
+
+export const EditProfileForm: React.FC = () => {
+ const queryClient = useQueryClient();
+
+ const { user, metadata } = useCurrentUser();
+ const { mutateAsync: publishEvent, isPending } = useNostrPublish();
+ const { toast } = useToast();
+
+ // Initialize the form with default values
+ const form = useForm({
+ resolver: zodResolver(n.metadata()),
+ defaultValues: {
+ name: '',
+ about: '',
+ picture: '',
+ banner: '',
+ website: '',
+ nip05: '',
+ bot: false,
+ },
+ });
+
+ // Update form values when user data is loaded
+ useEffect(() => {
+ if (metadata) {
+ form.reset({
+ name: metadata.name || '',
+ about: metadata.about || '',
+ picture: metadata.picture || '',
+ banner: metadata.banner || '',
+ website: metadata.website || '',
+ nip05: metadata.nip05 || '',
+ bot: metadata.bot || false,
+ });
+ }
+ }, [metadata, form]);
+
+ const onSubmit = async (values: NostrMetadata) => {
+ if (!user) {
+ toast({
+ title: 'Error',
+ description: 'You must be logged in to update your profile',
+ variant: 'destructive',
+ });
+ return;
+ }
+
+ try {
+ // Combine existing metadata with new values
+ const data = { ...metadata, ...values };
+
+ // Clean up empty values
+ for (const key in data) {
+ if (data[key] === '') {
+ delete data[key];
+ }
+ }
+
+ // Publish the metadata event (kind 0)
+ await publishEvent({
+ kind: 0,
+ content: JSON.stringify(data),
+ });
+
+ // Invalidate queries to refresh the data
+ queryClient.invalidateQueries({ queryKey: ['logins'] });
+ queryClient.invalidateQueries({ queryKey: ['author', user.pubkey] });
+
+ toast({
+ title: 'Success',
+ description: 'Your profile has been updated',
+ });
+ } catch (error) {
+ console.error('Failed to update profile:', error);
+ toast({
+ title: 'Error',
+ description: 'Failed to update your profile. Please try again.',
+ variant: 'destructive',
+ });
+ }
+ };
+
+ return (
+
+
+ );
+};
diff --git a/src/hooks/useCurrentUser.ts b/src/hooks/useCurrentUser.ts
index 4506139..e79b3a3 100644
--- a/src/hooks/useCurrentUser.ts
+++ b/src/hooks/useCurrentUser.ts
@@ -38,11 +38,11 @@ export function useCurrentUser() {
}, [logins, loginToUser]);
const user = users[0] as NUser | undefined;
- const data = useAuthor(user?.pubkey);
+ const author = useAuthor(user?.pubkey);
return {
user,
- data,
users,
+ ...author.data,
};
}