From 78a9fffafadd35d1e084c750bcb5418a59160473 Mon Sep 17 00:00:00 2001 From: mtyszczak <mateusz.tyszczak@gmail.com> Date: Wed, 12 Mar 2025 12:56:12 +0100 Subject: [PATCH] Add menu --- index.html | 2 +- src/App.vue | 28 +++--- src/components/AppHeader.vue | 3 - src/components/sidebar/AppSidebar.vue | 86 +++++++++++++++++++ src/components/sidebar/ToggleSidebar.vue | 16 ++++ src/components/sidebar/index.ts | 1 + src/components/ui/avatar/Avatar.vue | 21 +++++ src/components/ui/avatar/AvatarFallback.vue | 11 +++ src/components/ui/avatar/AvatarImage.vue | 12 +++ src/components/ui/avatar/index.ts | 24 ++++++ src/components/utilcards/AuthorityCard.vue | 4 +- .../utilcards/ConfirmCreateAccountCard.vue | 4 +- src/components/utilcards/MemoEncryptCard.vue | 4 +- .../utilcards/SignTransactionCard.vue | 4 +- src/main.ts | 4 +- src/pages/account/create.vue | 9 ++ src/pages/index.vue | 8 +- src/pages/sign/message.vue | 9 ++ src/pages/sign/transaction.vue | 9 ++ src/utils/router.ts | 8 +- 20 files changed, 232 insertions(+), 35 deletions(-) create mode 100644 src/components/sidebar/AppSidebar.vue create mode 100644 src/components/sidebar/ToggleSidebar.vue create mode 100644 src/components/sidebar/index.ts create mode 100644 src/components/ui/avatar/Avatar.vue create mode 100644 src/components/ui/avatar/AvatarFallback.vue create mode 100644 src/components/ui/avatar/AvatarImage.vue create mode 100644 src/components/ui/avatar/index.ts create mode 100644 src/pages/account/create.vue create mode 100644 src/pages/sign/message.vue create mode 100644 src/pages/sign/transaction.vue diff --git a/index.html b/index.html index 435899f..f875fc8 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Hive Bridge</title> </head> - <body> + <body class="dark"> <div id="app"></div> <script type="module" src="/src/main.ts"></script> </body> diff --git a/src/App.vue b/src/App.vue index 32f2705..50cbd6f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,7 +2,9 @@ import { ref, onMounted, defineAsyncComponent } from 'vue'; import { useSettingsStore, UsedWallet } from '@/stores/settings.store'; import { useWalletStore } from '@/stores/wallet.store'; -import AppHeader from '@/components/AppHeader.vue'; +import AppSidebar from '@/components/sidebar'; +import { SidebarProvider } from '@/components/ui/sidebar'; +import ToggleSidebar from './components/sidebar/ToggleSidebar.vue'; const WalletOnboarding = defineAsyncComponent(() => import('@/components/onboarding/index')); @@ -27,15 +29,19 @@ const complete = (data: { account: string; wallet: UsedWallet }) => { </script> <template> - <div id="shadcn-root" class="dark"> + <div id="shadcn-root"> <div id="app-main"> - <AppHeader/> - <main class="mt-[65px]"> - <RouterView /> - </main> - <aside v-if="settingsStore.isLoaded && !hasUser" class="fixed inset-0 flex items-center justify-center z-20"> - <WalletOnboarding @complete="complete" /> - </aside> + <SidebarProvider> + <AppSidebar/> + <!-- <AppHeader/> --> + <main class="w-full"> + <ToggleSidebar class="m-3" /> + <RouterView /> + </main> + <aside v-if="settingsStore.isLoaded && !hasUser" class="fixed inset-0 flex items-center justify-center z-20"> + <WalletOnboarding @complete="complete" /> + </aside> + </SidebarProvider> </div> </div> </template> @@ -43,8 +49,6 @@ const complete = (data: { account: string; wallet: UsedWallet }) => { <style scoped> #app-main { overflow-y: auto; - height: 100%; - width: 100%; background: url('/bg.svg') no-repeat center center fixed; background-size: cover; } @@ -52,8 +56,6 @@ const complete = (data: { account: string; wallet: UsedWallet }) => { #shadcn-root { background-color: hsl(var(--background)); color: hsl(var(--foreground)); - height: 100vh; - width: 100%; overflow: hidden; font-family: Verdana, sans-serif; } diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue index 5a19c7f..8e48a9b 100644 --- a/src/components/AppHeader.vue +++ b/src/components/AppHeader.vue @@ -17,9 +17,6 @@ const logout = () => { <header class="fixed top-0 left-0 z-10 bg-black/60 backdrop-blur-sm w-full"> <nav class="flex items-center relative sm:justify-center p-4 w-full h-[65px] border-b-[1px] border-white/25"> <div class="hidden sm:inline absolute top-[14px] left-[30px]" v-if="hasUser"> - <Button variant="outline" class="cursor-default" v-if="settingsStore.isLoaded && hasUser"> - <span class="font-bold">@{{ settingsStore.settings.account }}</span> - </Button> </div> <div class="flex items-center space-x-4"> <img src="/icon.svg" class="h-8 w-8" /> diff --git a/src/components/sidebar/AppSidebar.vue b/src/components/sidebar/AppSidebar.vue new file mode 100644 index 0000000..05fa72a --- /dev/null +++ b/src/components/sidebar/AppSidebar.vue @@ -0,0 +1,86 @@ +<script setup lang="ts"> +import { mdiHomeOutline, mdiMessageLockOutline, mdiFileSign, mdiAccountPlusOutline } from "@mdi/js" +import { Sidebar, SidebarContent, SidebarHeader, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar" +import { useSidebar } from "@/components/ui/sidebar"; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { useSettingsStore, getWalletIcon } from '@/stores/settings.store'; +import { computed } from 'vue'; +import { Button } from '@/components/ui/button'; + +const settingsStore = useSettingsStore(); +const hasUser = computed(() => settingsStore.settings.account !== undefined); + +const logout = () => { + settingsStore.resetSettings(); + window.location.reload(); +}; + +const { toggleSidebar, isMobile } = useSidebar(); + +const items = [ + { + title: "Home", + url: "/", + icon: mdiHomeOutline, + }, + { + title: "Memo encryption", + url: "/sign/message", + icon: mdiMessageLockOutline, + }, + { + title: "Transaction signing", + url: "/sign/transaction", + icon: mdiFileSign, + }, + { + title: "Process Account Creation", + url: "/account/create", + icon: mdiAccountPlusOutline, + } +]; +</script> + +<template> + <Sidebar> + <SidebarHeader class="pb-0"> + <div class="flex items-center rounded-lg p-2 mt-1 mx-1 bg-black/40 border" v-if="settingsStore.isLoaded && hasUser"> + <Avatar class="w-8 h-8 mr-2"> + <AvatarImage src="https://github.com/unovue.png" alt="@unovue" /> + <AvatarFallback>{{ settingsStore.settings.account?.slice(0, 2) }}</AvatarFallback> + </Avatar> + <span class="font-bold">@{{ settingsStore.settings.account }}</span> + </div> + </SidebarHeader> + <SidebarContent> + <SidebarGroup> + <SidebarGroupLabel>Hive Bridge</SidebarGroupLabel> + <SidebarGroupContent> + <SidebarMenu> + <SidebarMenuItem v-for="item in items" :key="item.title"> + <SidebarMenuButton asChild> + <RouterLink @click="isMobile && toggleSidebar()" :to="item.url"> + <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsl(var(--foreground))" :d="item.icon"/></svg> + <span>{{item.title}}</span> + </RouterLink> + </SidebarMenuButton> + </SidebarMenuItem> + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> + </SidebarContent> + <SidebarFooter> + <Button class="bg-black/40" variant="outline" v-if="settingsStore.isLoaded && hasUser" @click="logout"> + <img v-if="hasUser" :src="getWalletIcon(settingsStore.settings.wallet!)" class="h-6 w-6" /> + <span class="font-bold">Disconnect</span> + </Button> + </SidebarFooter> + </Sidebar> +</template> + +<style> +[data-sidebar="sidebar"] { + backdrop-filter: blur(20px); + background-color: rgba(0, 0, 0, 0.4); +} +</style> diff --git a/src/components/sidebar/ToggleSidebar.vue b/src/components/sidebar/ToggleSidebar.vue new file mode 100644 index 0000000..69da26a --- /dev/null +++ b/src/components/sidebar/ToggleSidebar.vue @@ -0,0 +1,16 @@ +<script setup lang="ts"> +import { useSidebar } from "@/components/ui/sidebar"; +import { Button } from "@/components/ui/button"; +import { mdiMenuClose, mdiMenuOpen } from "@mdi/js"; + +const { toggleSidebar, open, isMobile } = useSidebar(); +</script> + +<template> + <Button variant="ghost" size="xs" @click="toggleSidebar"> + <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <path style="fill: hsl(var(--foreground))" :d="open && !isMobile ? mdiMenuOpen : mdiMenuClose"></path> + </svg> + <span>{{ open && !isMobile ? 'Close sidebar' : 'Open sidebar' }}</span> + </Button> +</template> diff --git a/src/components/sidebar/index.ts b/src/components/sidebar/index.ts new file mode 100644 index 0000000..3190525 --- /dev/null +++ b/src/components/sidebar/index.ts @@ -0,0 +1 @@ +export { default } from './AppSidebar.vue'; diff --git a/src/components/ui/avatar/Avatar.vue b/src/components/ui/avatar/Avatar.vue new file mode 100644 index 0000000..95100c8 --- /dev/null +++ b/src/components/ui/avatar/Avatar.vue @@ -0,0 +1,21 @@ +<script setup lang="ts"> +import type { HTMLAttributes } from 'vue' +import { cn } from '@/lib/utils' +import { AvatarRoot } from 'reka-ui' +import { avatarVariant, type AvatarVariants } from '.' + +const props = withDefaults(defineProps<{ + class?: HTMLAttributes['class'] + size?: AvatarVariants['size'] + shape?: AvatarVariants['shape'] +}>(), { + size: 'sm', + shape: 'circle', +}) +</script> + +<template> + <AvatarRoot :class="cn(avatarVariant({ size, shape }), props.class)"> + <slot /> + </AvatarRoot> +</template> diff --git a/src/components/ui/avatar/AvatarFallback.vue b/src/components/ui/avatar/AvatarFallback.vue new file mode 100644 index 0000000..a0b8dfa --- /dev/null +++ b/src/components/ui/avatar/AvatarFallback.vue @@ -0,0 +1,11 @@ +<script setup lang="ts"> +import { AvatarFallback, type AvatarFallbackProps } from 'reka-ui' + +const props = defineProps<AvatarFallbackProps>() +</script> + +<template> + <AvatarFallback v-bind="props"> + <slot /> + </AvatarFallback> +</template> diff --git a/src/components/ui/avatar/AvatarImage.vue b/src/components/ui/avatar/AvatarImage.vue new file mode 100644 index 0000000..8efeecb --- /dev/null +++ b/src/components/ui/avatar/AvatarImage.vue @@ -0,0 +1,12 @@ +<script setup lang="ts"> +import type { AvatarImageProps } from 'reka-ui' +import { AvatarImage } from 'reka-ui' + +const props = defineProps<AvatarImageProps>() +</script> + +<template> + <AvatarImage v-bind="props" class="h-full w-full object-cover"> + <slot /> + </AvatarImage> +</template> diff --git a/src/components/ui/avatar/index.ts b/src/components/ui/avatar/index.ts new file mode 100644 index 0000000..5367952 --- /dev/null +++ b/src/components/ui/avatar/index.ts @@ -0,0 +1,24 @@ +import { cva, type VariantProps } from 'class-variance-authority' + +export { default as Avatar } from './Avatar.vue' +export { default as AvatarFallback } from './AvatarFallback.vue' +export { default as AvatarImage } from './AvatarImage.vue' + +export const avatarVariant = cva( + 'inline-flex items-center justify-center font-normal text-foreground select-none shrink-0 bg-secondary overflow-hidden', + { + variants: { + size: { + sm: 'h-10 w-10 text-xs', + base: 'h-16 w-16 text-2xl', + lg: 'h-32 w-32 text-5xl', + }, + shape: { + circle: 'rounded-full', + square: 'rounded-md', + }, + }, + }, +) + +export type AvatarVariants = VariantProps<typeof avatarVariant> diff --git a/src/components/utilcards/AuthorityCard.vue b/src/components/utilcards/AuthorityCard.vue index a7c1f09..0004f92 100644 --- a/src/components/utilcards/AuthorityCard.vue +++ b/src/components/utilcards/AuthorityCard.vue @@ -40,13 +40,13 @@ onMounted(() => { </script> <template> - <Card class="bg-black/40 backdrop-blur-sm"> + <Card class="w-full max-w-[600px] bg-black/40 backdrop-blur-sm"> <CardHeader> <CardTitle class="inline-flex items-center justify-between"> <span>Authority info</span> <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiAccountKeyOutline"/></svg> </CardTitle> - <CardDescription class="mr-4">Use this module to gather information about your Hive on-chain authorities</CardDescription> + <CardDescription class="mr-8">Use this module to gather information about your Hive on-chain authorities</CardDescription> </CardHeader> <CardContent> <div class="space-y-4" v-if="hasUser"> diff --git a/src/components/utilcards/ConfirmCreateAccountCard.vue b/src/components/utilcards/ConfirmCreateAccountCard.vue index 40ff809..6a4cf41 100644 --- a/src/components/utilcards/ConfirmCreateAccountCard.vue +++ b/src/components/utilcards/ConfirmCreateAccountCard.vue @@ -7,13 +7,13 @@ import { Input } from '@/components/ui/input'; </script> <template> - <Card class="bg-black/40 backdrop-blur-sm"> + <Card class="w-full max-w-[600px] bg-black/40 backdrop-blur-sm"> <CardHeader> <CardTitle class="inline-flex items-center justify-between"> <span>Process Account Creation</span> <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiAccountPlusOutline"/></svg> </CardTitle> - <CardDescription class="mr-4">Use this module to process account creation request sent by other users</CardDescription> + <CardDescription class="mr-8">Use this module to process account creation request sent by other users</CardDescription> </CardHeader> <CardContent> <div class="my-4"> diff --git a/src/components/utilcards/MemoEncryptCard.vue b/src/components/utilcards/MemoEncryptCard.vue index 0cd93dc..7694c82 100644 --- a/src/components/utilcards/MemoEncryptCard.vue +++ b/src/components/utilcards/MemoEncryptCard.vue @@ -51,13 +51,13 @@ const encryptOrDecrypt = async () => { </script> <template> - <Card class="bg-black/40 backdrop-blur-sm"> + <Card class="w-full max-w-[600px] bg-black/40 backdrop-blur-sm"> <CardHeader> <CardTitle class="inline-flex items-center justify-between"> <span>Memo encryption</span> <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiMessageLockOutline"/></svg> </CardTitle> - <CardDescription class="mr-4">Use this module to encrypt / decrypt given message using your memo key for any purpose</CardDescription> + <CardDescription class="mr-8">Use this module to encrypt / decrypt given message using your memo key for any purpose</CardDescription> </CardHeader> <CardContent> <div class="my-4 space-x-4 flex"> diff --git a/src/components/utilcards/SignTransactionCard.vue b/src/components/utilcards/SignTransactionCard.vue index aa738da..91ef8d4 100644 --- a/src/components/utilcards/SignTransactionCard.vue +++ b/src/components/utilcards/SignTransactionCard.vue @@ -35,13 +35,13 @@ const sign = async () => { </script> <template> - <Card class="bg-black/40 backdrop-blur-sm"> + <Card class="w-full max-w-[600px] bg-black/40 backdrop-blur-sm"> <CardHeader> <CardTitle class="inline-flex items-center justify-between"> <span>Transaction signing</span> <svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill: hsla(var(--foreground) / 80%)" :d="mdiFileSign"/></svg> </CardTitle> - <CardDescription class="mr-4">Use this module to sign the provided transaction</CardDescription> + <CardDescription class="mr-8">Use this module to sign the provided transaction</CardDescription> </CardHeader> <CardContent> <Textarea v-model="inputData" placeholder="Transaction in API JSON form" class="my-4"/> diff --git a/src/main.ts b/src/main.ts index f266bd2..4a9c74d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,14 +1,14 @@ import { createApp } from 'vue' import './style.css' import { createPinia } from 'pinia' -import { createMemoryHistory, createRouter } from 'vue-router' +import { createWebHistory, createRouter } from 'vue-router' import App from './App.vue' import { routes } from './utils/router' const pinia = createPinia() const router = createRouter({ routes: routes, - history: createMemoryHistory(), + history: createWebHistory(), }) createApp(App).use(pinia).use(router).mount('#app') diff --git a/src/pages/account/create.vue b/src/pages/account/create.vue new file mode 100644 index 0000000..df00753 --- /dev/null +++ b/src/pages/account/create.vue @@ -0,0 +1,9 @@ +<script setup lang="ts"> +import ConfirmCreateAccountCard from '@/components/utilcards/ConfirmCreateAccountCard.vue'; +</script> + +<template> + <div class="flex py-4 px-8"> + <ConfirmCreateAccountCard /> + </div> +</template> diff --git a/src/pages/index.vue b/src/pages/index.vue index effef12..9e58655 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -1,15 +1,9 @@ <script setup lang="ts"> import AuthorityCard from '@/components/utilcards/AuthorityCard.vue'; -import ConfirmCreateAccountCard from '@/components/utilcards/ConfirmCreateAccountCard.vue'; -import MemoEncryptCard from '@/components/utilcards/MemoEncryptCard.vue'; -import SignTransactionCard from '@/components/utilcards/SignTransactionCard.vue'; </script> <template> - <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 p-8"> + <div class="flex py-4 px-8"> <AuthorityCard /> - <MemoEncryptCard /> - <SignTransactionCard /> - <ConfirmCreateAccountCard /> </div> </template> diff --git a/src/pages/sign/message.vue b/src/pages/sign/message.vue new file mode 100644 index 0000000..f5c5e2c --- /dev/null +++ b/src/pages/sign/message.vue @@ -0,0 +1,9 @@ +<script setup lang="ts"> +import MemoEncryptCard from '@/components/utilcards/MemoEncryptCard.vue'; +</script> + +<template> + <div class="flex py-4 px-8"> + <MemoEncryptCard /> + </div> +</template> diff --git a/src/pages/sign/transaction.vue b/src/pages/sign/transaction.vue new file mode 100644 index 0000000..d8dea38 --- /dev/null +++ b/src/pages/sign/transaction.vue @@ -0,0 +1,9 @@ +<script setup lang="ts"> +import SignTransactionCard from '@/components/utilcards/SignTransactionCard.vue'; +</script> + +<template> + <div class="flex py-4 px-8"> + <SignTransactionCard /> + </div> +</template> diff --git a/src/utils/router.ts b/src/utils/router.ts index 5cc878e..1d5a8fa 100644 --- a/src/utils/router.ts +++ b/src/utils/router.ts @@ -1,5 +1,11 @@ import Index from "@/pages/index.vue"; +import SignTransaction from "@/pages/sign/transaction.vue"; +import SignMessage from "@/pages/sign/message.vue"; +import AccountCreate from "@/pages/account/create.vue"; export const routes = [ - { path: '/', component: Index } + { path: '/', component: Index }, + { path: '/sign/transaction', component: SignTransaction }, + { path: '/sign/message', component: SignMessage }, + { path: '/account/create', component: AccountCreate } ]; -- GitLab