story-research-zapwall/components/UnlockAccountModal.tsx

160 lines
4.5 KiB
TypeScript

import { useState } from 'react'
import { nostrAuthService } from '@/lib/nostrAuth'
interface UnlockAccountModalProps {
onSuccess: () => void
onClose: () => void
}
function WordInputs({
words,
onWordChange,
}: {
words: string[]
onWordChange: (index: number, value: string) => void
}) {
return (
<div className="grid grid-cols-2 gap-4">
{words.map((word, index) => (
<div key={index}>
<label htmlFor={`word-${index}`} className="block text-sm font-medium text-gray-700 mb-2">
Mot {index + 1}
</label>
<input
id={`word-${index}`}
type="text"
value={word}
onChange={(e) => onWordChange(index, e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg font-mono text-lg text-center"
autoComplete="off"
autoCapitalize="off"
autoCorrect="off"
spellCheck="false"
/>
</div>
))}
</div>
)
}
function useUnlockAccount(words: string[], setWords: (words: string[]) => void, setError: (error: string | null) => void) {
const handleWordChange = (index: number, value: string) => {
const newWords = [...words]
newWords[index] = value.trim().toLowerCase()
setWords(newWords)
setError(null)
}
const handlePaste = async () => {
try {
const text = await navigator.clipboard.readText()
const pastedWords = text.trim().split(/\s+/).slice(0, 4)
if (pastedWords.length === 4) {
setWords(pastedWords.map((w) => w.toLowerCase()))
setError(null)
}
} catch (_e) {
// Ignore clipboard errors
}
}
return { handleWordChange, handlePaste }
}
function UnlockAccountButtons({
loading,
words,
onUnlock,
onClose,
}: {
loading: boolean
words: string[]
onUnlock: () => void
onClose: () => void
}) {
return (
<div className="flex gap-4">
<button
onClick={onClose}
className="flex-1 py-2 px-4 bg-gray-200 hover:bg-gray-300 rounded-lg font-medium transition-colors"
>
Annuler
</button>
<button
onClick={() => {
void onUnlock()
}}
disabled={loading || words.some((word) => !word)}
className="flex-1 py-2 px-4 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded-lg font-medium transition-all border border-neon-cyan/50 hover:shadow-glow-cyan disabled:opacity-50"
>
{loading ? 'Déverrouillage...' : 'Déverrouiller'}
</button>
</div>
)
}
function UnlockAccountForm({
words,
handleWordChange,
handlePaste,
}: {
words: string[]
handleWordChange: (index: number, value: string) => void
handlePaste: () => void
}) {
return (
<div className="mb-4">
<WordInputs words={words} onWordChange={handleWordChange} />
<button
onClick={() => {
void handlePaste()
}}
className="mt-2 text-sm text-gray-600 hover:text-gray-800 underline"
>
Coller depuis le presse-papiers
</button>
</div>
)
}
export function UnlockAccountModal({ onSuccess, onClose }: UnlockAccountModalProps) {
const [words, setWords] = useState(['', '', '', ''])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const { handleWordChange, handlePaste } = useUnlockAccount(words, setWords, setError)
const handleUnlock = async () => {
if (words.some((word) => !word)) {
setError('Veuillez remplir tous les mots-clés')
return
}
setLoading(true)
setError(null)
try {
await nostrAuthService.unlockAccount(words)
onSuccess()
onClose()
} catch (e) {
setError(e instanceof Error ? e.message : 'Échec du déverrouillage. Vérifiez vos mots-clés.')
} finally {
setLoading(false)
}
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
<h2 className="text-2xl font-bold mb-4">Déverrouiller votre compte</h2>
<p className="text-gray-600 mb-6">
Entrez vos 4 mots-clés de récupération pour déverrouiller votre compte.
</p>
<UnlockAccountForm words={words} handleWordChange={handleWordChange} handlePaste={handlePaste} />
{error && <p className="text-sm text-red-600 mb-4">{error}</p>}
<UnlockAccountButtons loading={loading} words={words} onUnlock={handleUnlock} onClose={onClose} />
</div>
</div>
)
}