Fix: profil image2
This commit is contained in:
parent
94ac35f309
commit
a058056475
@ -2,6 +2,7 @@ import { useState } from 'react'
|
||||
import { uploadNip95Media } from '@/lib/nip95'
|
||||
import { t } from '@/lib/i18n'
|
||||
import Image from 'next/image'
|
||||
import { UnlockAccountModal } from './UnlockAccountModal'
|
||||
|
||||
interface ImageUploadFieldProps {
|
||||
id: string
|
||||
@ -94,6 +95,8 @@ async function processFileUpload(file: File, onChange: (url: string) => void, se
|
||||
function useImageUpload(onChange: (url: string) => void) {
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [showUnlockModal, setShowUnlockModal] = useState(false)
|
||||
const [pendingFile, setPendingFile] = useState<File | null>(null)
|
||||
|
||||
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
@ -107,21 +110,47 @@ function useImageUpload(onChange: (url: string) => void) {
|
||||
try {
|
||||
await processFileUpload(file, onChange, setError)
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : t('presentation.field.picture.error.uploadFailed'))
|
||||
const error = e instanceof Error ? e : new Error(String(e))
|
||||
// Check if unlock is required
|
||||
if (error.message === 'UNLOCK_REQUIRED' || (error as any).unlockRequired) {
|
||||
setPendingFile(file)
|
||||
setShowUnlockModal(true)
|
||||
setError(null) // Don't show error, show unlock modal instead
|
||||
} else {
|
||||
setError(error.message || t('presentation.field.picture.error.uploadFailed'))
|
||||
}
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return { uploading, error, handleFileSelect }
|
||||
const handleUnlockSuccess = async () => {
|
||||
setShowUnlockModal(false)
|
||||
if (pendingFile) {
|
||||
// Retry upload after unlock
|
||||
setUploading(true)
|
||||
setError(null)
|
||||
try {
|
||||
await processFileUpload(pendingFile, onChange, setError)
|
||||
setPendingFile(null)
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : t('presentation.field.picture.error.uploadFailed'))
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { uploading, error, handleFileSelect, showUnlockModal, setShowUnlockModal, handleUnlockSuccess }
|
||||
}
|
||||
|
||||
export function ImageUploadField({ id, label, value, onChange, helpText }: ImageUploadFieldProps) {
|
||||
const { uploading, error, handleFileSelect } = useImageUpload(onChange)
|
||||
const { uploading, error, handleFileSelect, showUnlockModal, setShowUnlockModal, handleUnlockSuccess } = useImageUpload(onChange)
|
||||
const displayLabel = label ?? t('presentation.field.picture')
|
||||
const displayHelpText = helpText ?? t('presentation.field.picture.help')
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label htmlFor={id} className="block text-sm font-medium text-neon-cyan">
|
||||
{displayLabel}
|
||||
@ -137,5 +166,14 @@ export function ImageUploadField({ id, label, value, onChange, helpText }: Image
|
||||
{error && <p className="text-sm text-red-400">{error}</p>}
|
||||
{displayHelpText && <p className="text-sm text-cyber-accent">{displayHelpText}</p>}
|
||||
</div>
|
||||
{showUnlockModal && (
|
||||
<UnlockAccountModal
|
||||
onSuccess={handleUnlockSuccess}
|
||||
onClose={() => {
|
||||
setShowUnlockModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
21
components/KeyIndicator.tsx
Normal file
21
components/KeyIndicator.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { useNostrAuth } from '@/hooks/useNostrAuth'
|
||||
|
||||
export function KeyIndicator() {
|
||||
const { pubkey, isUnlocked } = useNostrAuth()
|
||||
|
||||
// No indicator if no account
|
||||
if (!pubkey) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Red if private key is accessible (unlocked)
|
||||
// Green if only public key is accessible (connected but not unlocked)
|
||||
const color = isUnlocked ? 'text-red-500' : 'text-green-500'
|
||||
const title = isUnlocked ? 'Private key accessible' : 'Public key accessible'
|
||||
|
||||
return (
|
||||
<span className={`ml-2 text-xl ${color}`} title={title}>
|
||||
🔑
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -2,13 +2,15 @@ import Link from 'next/link'
|
||||
import { ConditionalPublishButton } from './ConditionalPublishButton'
|
||||
import { LanguageSelector } from './LanguageSelector'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { KeyIndicator } from './KeyIndicator'
|
||||
|
||||
export function PageHeader() {
|
||||
return (
|
||||
<header className="bg-cyber-dark border-b border-neon-cyan/30 shadow-glow-cyan">
|
||||
<div className="max-w-4xl mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<Link href="/" className="text-2xl font-bold text-neon-cyan text-glow-cyan font-mono hover:text-neon-green transition-colors">
|
||||
<Link href="/" className="text-2xl font-bold text-neon-cyan text-glow-cyan font-mono hover:text-neon-green transition-colors flex items-center">
|
||||
{t('home.title')}
|
||||
<KeyIndicator />
|
||||
</Link>
|
||||
<div className="flex items-center gap-4">
|
||||
<LanguageSelector />
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import { ConnectButton } from '@/components/ConnectButton'
|
||||
import { ConditionalPublishButton } from './ConditionalPublishButton'
|
||||
import { KeyIndicator } from './KeyIndicator'
|
||||
|
||||
export function ProfileHeader() {
|
||||
return (
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className="max-w-4xl mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900">zapwall.fr</h1>
|
||||
<h1 className="text-2xl font-bold text-gray-900 flex items-center">
|
||||
zapwall.fr
|
||||
<KeyIndicator />
|
||||
</h1>
|
||||
<div className="flex items-center gap-4">
|
||||
<ConditionalPublishButton />
|
||||
<ConnectButton />
|
||||
|
||||
@ -42,7 +42,7 @@ export const DEFAULT_NIP95_APIS: Nip95Config[] = [
|
||||
{
|
||||
id: 'nostrimg',
|
||||
url: 'https://nostrimg.com/api/upload',
|
||||
enabled: true, // Temporarily enabled for diagnostic logging - disable if 500 errors persist
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
|
||||
14
lib/nip95.ts
14
lib/nip95.ts
@ -137,13 +137,18 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
||||
const isUnlocked = nostrAuthService.isUnlocked()
|
||||
if (!pubkey) {
|
||||
console.warn('NIP-98 authentication required for nostrcheck.me but no account found. Please create or import an account.')
|
||||
continue
|
||||
} else if (!isUnlocked) {
|
||||
console.warn('NIP-98 authentication required for nostrcheck.me but account is not unlocked. Please unlock your account with your recovery phrase to use this endpoint.')
|
||||
// Throw a special error that can be caught to trigger unlock modal
|
||||
// This error should propagate to the caller, not be caught here
|
||||
const unlockError = new Error('UNLOCK_REQUIRED')
|
||||
;(unlockError as any).unlockRequired = true
|
||||
throw unlockError
|
||||
} else {
|
||||
console.warn('NIP-98 authentication required for nostrcheck.me but not available. Skipping endpoint.')
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Generate NIP-98 token for the actual endpoint (not the proxy)
|
||||
// The token must be for the final destination URL
|
||||
@ -170,6 +175,11 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
||||
const error = e instanceof Error ? e : new Error(String(e))
|
||||
const errorMessage = error.message
|
||||
|
||||
// If unlock is required, propagate the error immediately
|
||||
if (errorMessage === 'UNLOCK_REQUIRED' || (error as any).unlockRequired) {
|
||||
throw error
|
||||
}
|
||||
|
||||
console.error('NIP-95 upload endpoint error:', {
|
||||
endpoint,
|
||||
error: errorMessage,
|
||||
|
||||
@ -209,6 +209,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
})
|
||||
|
||||
requestFormData.on('error', (error) => {
|
||||
console.error('NIP-95 proxy FormData error:', {
|
||||
targetEndpoint,
|
||||
hostname: url.hostname,
|
||||
error: error instanceof Error ? error.message : 'Unknown FormData error',
|
||||
})
|
||||
reject(error)
|
||||
})
|
||||
|
||||
fileStream.on('error', (error) => {
|
||||
console.error('NIP-95 proxy file stream error:', {
|
||||
targetEndpoint,
|
||||
hostname: url.hostname,
|
||||
filepath: fileField.filepath,
|
||||
error: error instanceof Error ? error.message : 'Unknown file stream error',
|
||||
})
|
||||
reject(error)
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user