feat: generate new account & profile editor

This commit is contained in:
codytseng
2025-01-14 18:09:31 +08:00
parent 3f031da748
commit 78629dd64f
33 changed files with 535 additions and 142 deletions

View File

@@ -7,7 +7,7 @@ import { useToast } from '@/hooks/use-toast'
import { createCommentDraftEvent, createShortTextNoteDraftEvent } from '@/lib/draft-event'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
import { ChevronDown, LoaderCircle } from 'lucide-react'
import { ChevronDown, ImageUp, LoaderCircle } from 'lucide-react'
import { Event, kinds } from 'nostr-tools'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -32,6 +32,7 @@ export default function NormalPostContent({
const [posting, setPosting] = useState(false)
const [showMoreOptions, setShowMoreOptions] = useState(false)
const [addClientTag, setAddClientTag] = useState(false)
const [uploadingPicture, setUploadingPicture] = useState(false)
const canPost = !!content && !posting
useEffect(() => {
@@ -116,7 +117,13 @@ export default function NormalPostContent({
setPictureInfos((prev) => [...prev, { url, tags }])
setContent((prev) => `${prev}\n${url}`)
}}
/>
onUploadingChange={setUploadingPicture}
accept="image/*,video/*,audio/*"
>
<Button variant="secondary" disabled={uploadingPicture}>
{uploadingPicture ? <LoaderCircle className="animate-spin" /> : <ImageUp />}
</Button>
</Uploader>
<Button
variant="link"
className="text-foreground gap-0 px-0"

View File

@@ -5,8 +5,9 @@ import { Textarea } from '@/components/ui/textarea'
import { StorageKey } from '@/constants'
import { useToast } from '@/hooks/use-toast'
import { createPictureNoteDraftEvent } from '@/lib/draft-event'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { ChevronDown, LoaderCircle, X } from 'lucide-react'
import { ChevronDown, Loader, LoaderCircle, Plus, X } from 'lucide-react'
import { Dispatch, SetStateAction, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
@@ -177,6 +178,7 @@ function PictureUploader({
>
}) {
const [index, setIndex] = useState(-1)
const [uploading, setUploading] = useState(false)
return (
<>
@@ -203,11 +205,20 @@ function PictureUploader({
</div>
))}
<Uploader
variant="big"
onUploadSuccess={({ url, tags }) => {
setPictureInfos((prev) => [...prev, { url, tags }])
}}
/>
onUploadingChange={setUploading}
>
<div
className={cn(
'flex flex-col gap-2 items-center justify-center aspect-square w-full rounded-lg border border-dashed',
uploading ? 'cursor-not-allowed text-muted-foreground' : 'clickable'
)}
>
{uploading ? <Loader size={36} className="animate-spin" /> : <Plus size={36} />}
</div>
</Uploader>
</div>
{index >= 0 &&
createPortal(

View File

@@ -1,19 +1,21 @@
import { Button } from '@/components/ui/button'
import { useToast } from '@/hooks/use-toast'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { ImageUp, Loader, LoaderCircle, Plus } from 'lucide-react'
import { useRef, useState } from 'react'
import { useRef } from 'react'
import { z } from 'zod'
export default function Uploader({
children,
onUploadSuccess,
variant = 'button'
onUploadingChange,
className,
accept = 'image/*'
}: {
children: React.ReactNode
onUploadSuccess: ({ url, tags }: { url: string; tags: string[][] }) => void
variant?: 'button' | 'big'
onUploadingChange?: (uploading: boolean) => void
className?: string
accept?: string
}) {
const [uploading, setUploading] = useState(false)
const { signHttpAuth } = useNostr()
const { toast } = useToast()
const fileInputRef = useRef<HTMLInputElement>(null)
@@ -26,7 +28,7 @@ export default function Uploader({
formData.append('file', file)
try {
setUploading(true)
onUploadingChange?.(true)
const url = 'https://nostr.build/api/v2/nip96/upload'
const auth = await signHttpAuth(url, 'POST')
const response = await fetch(url, {
@@ -60,7 +62,7 @@ export default function Uploader({
fileInputRef.current.value = ''
}
} finally {
setUploading(false)
onUploadingChange?.(false)
}
}
@@ -71,41 +73,16 @@ export default function Uploader({
}
}
if (variant === 'button') {
return (
<>
<Button variant="secondary" onClick={handleUploadClick} disabled={uploading}>
{uploading ? <LoaderCircle className="animate-spin" /> : <ImageUp />}
</Button>
<input
type="file"
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFileChange}
accept="image/*,video/*,audio/*"
/>
</>
)
}
return (
<>
<div
className={cn(
'flex flex-col gap-2 items-center justify-center aspect-square w-full rounded-lg border border-dashed',
uploading ? 'cursor-not-allowed text-muted-foreground' : 'clickable'
)}
onClick={handleUploadClick}
>
{uploading ? <Loader size={36} className="animate-spin" /> : <Plus size={36} />}
</div>
<div onClick={handleUploadClick} className={className}>
{children}
<input
type="file"
ref={fileInputRef}
style={{ display: 'none' }}
onChange={handleFileChange}
accept="image/*"
accept={accept}
/>
</>
</div>
)
}