implement logout dialog, fix unused imports

This commit is contained in:
padreug 2025-02-11 15:50:33 +01:00
parent d81357ead1
commit 3aa8050b3f
15 changed files with 475 additions and 99 deletions

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import { DialogRoot } from 'radix-vue'
const props = defineProps<{
open?: boolean
defaultOpen?: boolean
modal?: boolean
}>()
const emits = defineEmits<{
'update:open': [value: boolean]
}>()
</script>
<template>
<DialogRoot v-bind="props" @update:open="emits('update:open', $event)">
<slot />
</DialogRoot>
</template>

View file

@ -0,0 +1,32 @@
<script setup lang="ts">
import { DialogPortal, DialogOverlay, DialogContent as DialogContentPrimitive, DialogClose } from 'radix-vue'
import { X } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import type { HTMLAttributes } from 'vue'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<DialogPortal>
<DialogOverlay
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<DialogContentPrimitive
:class="cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
props.class
)"
>
<slot />
<DialogClose
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContentPrimitive>
</DialogPortal>
</template>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import { DialogDescription as DialogDescriptionPrimitive } from 'radix-vue'
import { cn } from '@/lib/utils'
import type { HTMLAttributes } from 'vue'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<DialogDescriptionPrimitive :class="cn('text-sm text-muted-foreground', props.class)">
<slot />
</DialogDescriptionPrimitive>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import type { HTMLAttributes } from 'vue'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,14 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import type { HTMLAttributes } from 'vue'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div :class="cn('flex flex-col space-y-1.5 text-center sm:text-left', props.class)">
<slot />
</div>
</template>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import { DialogTitle as DialogTitlePrimitive } from 'radix-vue'
import { cn } from '@/lib/utils'
import type { HTMLAttributes } from 'vue'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<DialogTitlePrimitive :class="cn('text-lg font-semibold leading-none tracking-tight', props.class)">
<slot />
</DialogTitlePrimitive>
</template>

View file

@ -0,0 +1,16 @@
<script setup lang="ts">
import { DialogTrigger as DialogTriggerPrimitive } from 'radix-vue'
import { cn } from '@/lib/utils'
import type { HTMLAttributes } from 'vue'
const props = defineProps<{
class?: HTMLAttributes['class']
asChild?: boolean
}>()
</script>
<template>
<DialogTriggerPrimitive v-bind="props" :class="cn(props.class)">
<slot />
</DialogTriggerPrimitive>
</template>

View file

@ -0,0 +1,17 @@
export { default as Dialog } from './Dialog.vue'
export { default as DialogContent } from './DialogContent.vue'
export { default as DialogDescription } from './DialogDescription.vue'
export { default as DialogFooter } from './DialogFooter.vue'
export { default as DialogHeader } from './DialogHeader.vue'
export { default as DialogTitle } from './DialogTitle.vue'
export { default as DialogTrigger } from './DialogTrigger.vue'
export type {
DialogRootEmits,
DialogRootProps,
DialogOverlayProps,
DialogPortalProps,
DialogContentProps,
DialogTitleProps,
DialogTriggerProps,
} from 'radix-vue'

View file

@ -0,0 +1,115 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useNostrStore } from '@/stores/nostr'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { LogOut, Copy, Check, ShieldAlert } from 'lucide-vue-next'
const props = defineProps<{
onLogout: () => void
}>()
const nostrStore = useNostrStore()
const isOpen = ref(false)
const hasCopied = ref(false)
const copyPrivateKey = async () => {
if (!nostrStore.account?.privkey) return
try {
await navigator.clipboard.writeText(nostrStore.account.privkey)
hasCopied.value = true
setTimeout(() => {
hasCopied.value = false
}, 2000)
} catch (err) {
console.error('Failed to copy private key:', err)
}
}
const handleLogout = () => {
isOpen.value = false
props.onLogout()
}
</script>
<template>
<Dialog v-model:open="isOpen">
<DialogTrigger as-child>
<Button variant="ghost" size="icon" class="text-muted-foreground hover:text-foreground">
<LogOut class="h-5 w-5" />
<span class="sr-only">Logout</span>
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-md">
<DialogHeader class="space-y-4">
<div class="mx-auto w-12 h-12 rounded-full bg-gradient-to-br from-red-500 to-orange-500 p-0.5 shadow-lg">
<div class="w-full h-full rounded-full bg-background flex items-center justify-center">
<ShieldAlert class="h-6 w-6 text-red-500" />
</div>
</div>
<div class="text-center space-y-2">
<DialogTitle class="text-xl font-semibold text-red-500">
Backup Required
</DialogTitle>
<DialogDescription>
If you haven't saved your private key, you will permanently lose access to this chat history. Make sure to copy and securely store your private key before logging out.
</DialogDescription>
</div>
</DialogHeader>
<div class="flex flex-col gap-4 py-6">
<Button
class="w-full"
variant="outline"
@click="copyPrivateKey"
>
<Copy v-if="!hasCopied" class="h-4 w-4 mr-2" />
<Check v-else class="h-4 w-4 mr-2" />
{{ hasCopied ? 'Copied!' : 'Copy Private Key' }}
</Button>
</div>
<DialogFooter class="flex flex-col sm:flex-row gap-2 sm:gap-3">
<Button
variant="ghost"
@click="() => isOpen = false"
class="flex-1 sm:flex-none"
>
Cancel
</Button>
<Button
variant="destructive"
@click="handleLogout"
class="flex-1 sm:flex-none"
>
<LogOut class="h-4 w-4 mr-2" />
Logout
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>
<style scoped>
/* Improved focus styles */
:focus-visible {
outline: 2px solid #cba6f7;
outline-offset: 2px;
transition: outline-offset 0.2s ease;
}
/* Enhanced button hover states */
button:not(:disabled):hover {
transform: translateY(-1px) scale(1.01);
}
button:not(:disabled):active {
transform: translateY(0) scale(0.99);
}
/* Smooth transitions */
* {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 300ms;
}
</style>

View file

@ -1,7 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { cn } from '@/lib/utils'
import { ExternalLink, Copy, Check } from 'lucide-vue-next'
interface Props {
sent: boolean