mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-27 13:09:22 +00:00
Add EditProfileForm
This commit is contained in:
parent
d05a78880a
commit
f77a6a4af3
20
CONTEXT.md
20
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 (
|
||||
<div>
|
||||
{/* you may want to wrap this in a layout or include other components depending on the project ... */}
|
||||
|
||||
<EditProfileForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
252
src/components/EditProfileForm.tsx
Normal file
252
src/components/EditProfileForm.tsx
Normal file
@ -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<NostrMetadata>({
|
||||
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 (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Your name" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your display name that will be displayed to others.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="about"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bio</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Tell others about yourself"
|
||||
className="resize-none"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
A short description about yourself.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="picture"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Profile Picture URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="https://example.com/profile.jpg" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
URL to your profile picture.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="banner"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Banner Image URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="https://example.com/banner.jpg" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
URL to a wide banner image for your profile.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="website"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Website</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="https://yourwebsite.com" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Your personal website or social media link.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nip05"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>NIP-05 Identifier</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="you@example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Your verified Nostr identifier.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bot"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Bot Account</FormLabel>
|
||||
<FormDescription>
|
||||
Mark this account as automated or a bot.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full md:w-auto"
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
Save Profile
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user