lots of login dialog and nostr subsription relate updates
This commit is contained in:
parent
ed1b4cb22a
commit
d694f9b645
8 changed files with 323 additions and 189 deletions
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
<div class="text-center space-y-2.5">
|
||||
<CardTitle class="text-2xl font-bold bg-gradient-to-r from-primary to-primary/80 inline-block text-transparent bg-clip-text">
|
||||
Nostr Login
|
||||
{{ t('login.title') }}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Login with your Nostr private key or generate a new one
|
||||
{{ t('login.description') }}
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
|
|
@ -26,43 +26,53 @@
|
|||
<div class="relative">
|
||||
<Input
|
||||
v-model="privkey"
|
||||
type="password"
|
||||
placeholder="Enter your private key"
|
||||
:type="showKey ? 'text' : 'password'"
|
||||
:placeholder="t('login.placeholder')"
|
||||
:class="[
|
||||
{ 'border-destructive': error },
|
||||
{ 'pr-24': privkey }, // Add padding when we have a value to prevent overlap with button
|
||||
{ 'pr-24': privkey },
|
||||
]"
|
||||
@keyup.enter="login"
|
||||
/>
|
||||
<Button
|
||||
v-if="privkey"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="absolute right-1 top-1 h-8"
|
||||
@click="copyKey"
|
||||
>
|
||||
<Check v-if="copied" class="h-4 w-4 text-green-500" />
|
||||
<Copy v-else class="h-4 w-4" />
|
||||
<span class="sr-only">Copy private key</span>
|
||||
</Button>
|
||||
<div class="absolute right-1 top-1 flex gap-1">
|
||||
<Button
|
||||
v-if="privkey"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
@click="toggleShowKey"
|
||||
>
|
||||
<Eye v-if="!showKey" class="h-4 w-4" />
|
||||
<EyeOff v-else class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="privkey"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
@click="copyKey"
|
||||
>
|
||||
<Check v-if="copied" class="h-4 w-4 text-green-500" />
|
||||
<Copy v-else class="h-4 w-4" />
|
||||
<span class="sr-only">{{ t('login.buttons.copy') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="error" class="text-sm text-destructive">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Recovery message -->
|
||||
<div v-if="showRecoveryMessage" class="text-sm text-muted-foreground bg-muted/50 p-3 rounded-lg">
|
||||
<p>
|
||||
Make sure to save your private key in a secure location. You can use it to recover your chat history on any device.
|
||||
</p>
|
||||
<p>{{ t('login.recovery.message') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Button @click="login" :disabled="!privkey || isLoading">
|
||||
<span v-if="isLoading" class="h-4 w-4 animate-spin rounded-full border-2 border-background border-r-transparent" />
|
||||
<span v-else>Login</span>
|
||||
<Loader2 v-if="isLoading" class="h-4 w-4 animate-spin" />
|
||||
<span v-else>{{ t('login.buttons.login') }}</span>
|
||||
</Button>
|
||||
<Button variant="outline" @click="generateKey">
|
||||
Generate New Key
|
||||
<Button variant="outline" @click="generateKey" :disabled="isLoading">
|
||||
{{ t('login.buttons.generate') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -71,12 +81,15 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useNostrStore } from '@/stores/nostr'
|
||||
import { KeyRound, Copy, Check } from 'lucide-vue-next'
|
||||
import { KeyRound, Copy, Check, Eye, EyeOff, Loader2 } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { isValidPrivateKey, formatPrivateKey } from '@/lib/nostr'
|
||||
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits<{
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
|
@ -87,25 +100,50 @@ const error = ref('')
|
|||
const isLoading = ref(false)
|
||||
const copied = ref(false)
|
||||
const showRecoveryMessage = ref(false)
|
||||
const showKey = ref(false)
|
||||
|
||||
const toggleShowKey = () => {
|
||||
showKey.value = !showKey.value
|
||||
}
|
||||
|
||||
const login = async () => {
|
||||
if (!privkey.value) return
|
||||
if (!privkey.value || isLoading.value) return
|
||||
|
||||
const formattedKey = formatPrivateKey(privkey.value)
|
||||
if (!isValidPrivateKey(formattedKey)) {
|
||||
error.value = t('login.error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isLoading.value = true
|
||||
await nostrStore.login(privkey.value)
|
||||
error.value = ''
|
||||
await nostrStore.login(formattedKey)
|
||||
emit('success')
|
||||
} catch (err) {
|
||||
console.error('Login failed:', err)
|
||||
error.value = 'Invalid private key'
|
||||
error.value = err instanceof Error ? err.message : t('login.error')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const generateKey = () => {
|
||||
privkey.value = window.NostrTools.generatePrivateKey()
|
||||
showRecoveryMessage.value = true
|
||||
if (isLoading.value) return
|
||||
|
||||
try {
|
||||
const newKey = window.NostrTools.generatePrivateKey()
|
||||
if (!isValidPrivateKey(newKey)) {
|
||||
throw new Error('Generated key is invalid')
|
||||
}
|
||||
privkey.value = newKey
|
||||
error.value = ''
|
||||
showRecoveryMessage.value = true
|
||||
showKey.value = true // Show the key when generated
|
||||
} catch (err) {
|
||||
console.error('Failed to generate key:', err)
|
||||
error.value = t('login.error')
|
||||
}
|
||||
}
|
||||
|
||||
const copyKey = async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue