160 lines
4.5 KiB
TypeScript
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>
|
|
)
|
|
}
|