implement logout dialog, fix unused imports
This commit is contained in:
parent
d81357ead1
commit
3aa8050b3f
15 changed files with 475 additions and 99 deletions
|
|
@ -79,7 +79,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
*/
|
||||
workbox.precacheAndRoute([{
|
||||
"url": "index.html",
|
||||
"revision": "0.2675lqganp8"
|
||||
"revision": "0.301fc7pcvng"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
|
|
|||
283
package-lock.json
generated
283
package-lock.json
generated
|
|
@ -13,8 +13,11 @@
|
|||
"clsx": "^2.1.1",
|
||||
"fuse.js": "^7.0.0",
|
||||
"lucide-vue-next": "^0.474.0",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"pinia": "^2.3.1",
|
||||
"radix-vue": "^1.9.13",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^9.14.2",
|
||||
|
|
@ -2019,32 +2022,6 @@
|
|||
"vue-demi": ">=0.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/vue/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@internationalized/date": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz",
|
||||
|
|
@ -2170,6 +2147,51 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ciphers": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz",
|
||||
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves/node_modules/@noble/hashes": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||
|
|
@ -2507,6 +2529,57 @@
|
|||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@scure/bip32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
|
||||
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "~1.1.0",
|
||||
"@noble/hashes": "~1.3.1",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip32/node_modules/@noble/curves": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip39": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "~1.3.0",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||
|
|
@ -3099,32 +3172,6 @@
|
|||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
|
|
@ -5331,6 +5378,38 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nostr-tools": {
|
||||
"version": "2.10.4",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.10.4.tgz",
|
||||
"integrity": "sha512-biU7sk+jxHgVASfobg2T5ttxOGGSt69wEVBC51sHHOEaKAAdzHBLV/I2l9Rf61UzClhliZwNouYhqIso4a3HYg==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@noble/ciphers": "^0.5.1",
|
||||
"@noble/curves": "1.2.0",
|
||||
"@noble/hashes": "1.3.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"nostr-wasm": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-wasm": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
|
||||
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
|
|
@ -5446,6 +5525,28 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
|
||||
"integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.6.3",
|
||||
"vue-demi": "^0.14.10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.7.0 || ^3.5.11"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
|
|
@ -5544,32 +5645,6 @@
|
|||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/radix-vue/node_modules/@vueuse/metadata": {
|
||||
"version": "10.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
|
||||
|
|
@ -6236,6 +6311,32 @@
|
|||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-variants": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.1.tgz",
|
||||
"integrity": "sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tailwind-merge": "2.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.x",
|
||||
"pnpm": ">=7.x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-variants/node_modules/tailwind-merge": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz",
|
||||
"integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.1.tgz",
|
||||
|
|
@ -6710,6 +6811,32 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "9.14.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.2.tgz",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"pinia": "^2.3.1",
|
||||
"radix-vue": "^1.9.13",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^9.14.2",
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { Send, AlertCircle } from 'lucide-vue-next'
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { Send } from 'lucide-vue-next'
|
||||
import MessageBubble from '@/components/ui/message-bubble/MessageBubble.vue'
|
||||
|
||||
const nostrStore = useNostrStore()
|
||||
|
|
@ -102,13 +102,6 @@ const sendMessage = async (event: Event) => {
|
|||
}
|
||||
}
|
||||
|
||||
const formatTime = (timestamp: number) => {
|
||||
return new Date(timestamp * 1000).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const formatDate = (timestamp: number) => {
|
||||
const date = new Date(timestamp * 1000)
|
||||
const today = new Date()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Menu, X, Sun, Moon, Zap, MessageSquareText, LogIn, LogOut } from 'lucide-vue-next'
|
||||
import { Menu, X, Sun, Moon, Zap, MessageSquareText, LogIn } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useTheme } from '@/components/theme-provider'
|
||||
import { useNostrStore } from '@/stores/nostr'
|
||||
import { useRouter } from 'vue-router'
|
||||
import LogoutDialog from '@/components/ui/logout-dialog/LogoutDialog.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { theme, setTheme } = useTheme()
|
||||
|
|
@ -36,12 +37,8 @@ const toggleLocale = () => {
|
|||
localStorage.setItem('locale', newLocale)
|
||||
}
|
||||
|
||||
const handleAuth = () => {
|
||||
if (nostrStore.isLoggedIn) {
|
||||
const handleLogout = () => {
|
||||
nostrStore.logout()
|
||||
} else {
|
||||
router.push('/support')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -79,9 +76,11 @@ const handleAuth = () => {
|
|||
{{ locale === 'en' ? '🇪🇸 ES' : '🇺🇸 EN' }}
|
||||
</Button>
|
||||
|
||||
<Button variant="ghost" size="icon" @click="handleAuth" class="text-muted-foreground hover:text-foreground">
|
||||
<LogIn v-if="!nostrStore.isLoggedIn" class="h-5 w-5" />
|
||||
<LogOut v-else class="h-5 w-5" />
|
||||
<template v-if="nostrStore.isLoggedIn">
|
||||
<LogoutDialog :onLogout="handleLogout" />
|
||||
</template>
|
||||
<Button v-else variant="ghost" size="icon" @click="router.push('/support')" class="text-muted-foreground hover:text-foreground">
|
||||
<LogIn class="h-5 w-5" />
|
||||
</Button>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
|
|
|
|||
19
src/components/ui/dialog/Dialog.vue
Normal file
19
src/components/ui/dialog/Dialog.vue
Normal 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>
|
||||
32
src/components/ui/dialog/DialogContent.vue
Normal file
32
src/components/ui/dialog/DialogContent.vue
Normal 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>
|
||||
15
src/components/ui/dialog/DialogDescription.vue
Normal file
15
src/components/ui/dialog/DialogDescription.vue
Normal 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>
|
||||
14
src/components/ui/dialog/DialogFooter.vue
Normal file
14
src/components/ui/dialog/DialogFooter.vue
Normal 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>
|
||||
14
src/components/ui/dialog/DialogHeader.vue
Normal file
14
src/components/ui/dialog/DialogHeader.vue
Normal 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>
|
||||
15
src/components/ui/dialog/DialogTitle.vue
Normal file
15
src/components/ui/dialog/DialogTitle.vue
Normal 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>
|
||||
16
src/components/ui/dialog/DialogTrigger.vue
Normal file
16
src/components/ui/dialog/DialogTrigger.vue
Normal 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>
|
||||
17
src/components/ui/dialog/index.ts
Normal file
17
src/components/ui/dialog/index.ts
Normal 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'
|
||||
115
src/components/ui/logout-dialog/LogoutDialog.vue
Normal file
115
src/components/ui/logout-dialog/LogoutDialog.vue
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue