diff --git a/index.html b/index.html index 435899fd466ea57b1a5b79898c784cf5addf3d64..f875fc86b248b2a8d21d6d8bb799fe47961bd544 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 32f2705f2826ec97229601189aaeae31cb239460..50cbd6ffc730c677af222bc2a22581907d6330df 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 5a19c7f7d338eed67d4996ec85778729befe56dc..8e48a9bd56e2ef11088a094747303730ea253285 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 0000000000000000000000000000000000000000..05fa72a7c5c85a3b98689bc53dfcee0ef43ab47b --- /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 0000000000000000000000000000000000000000..69da26a271dd7cc3a54a6190abd155fa0e0eb135 --- /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 0000000000000000000000000000000000000000..3190525b500aa3ad39f1c9dd3561630f08a08a61 --- /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 0000000000000000000000000000000000000000..95100c862cb5eb927f79c153b917b7fd6c92978b --- /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 0000000000000000000000000000000000000000..a0b8dfab0428db38ca318c520ddd79f5db881961 --- /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 0000000000000000000000000000000000000000..8efeecb0a3c5fc99936607710814ab3317b0232c --- /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 0000000000000000000000000000000000000000..5367952a013f6b5b85aa95de03fdb1c6062d57ee --- /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 a7c1f09940abf744ad8154d7774dfee33a89a801..0004f9280d7eaaeef1baeaef8d24a651ae288e8e 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 40ff809d5f8d402c78039acf0ab9a49983f42ad3..6a4cf41e03b38f243b933ebe155ee4c86539bf1b 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 0cd93dcd175d87cfbe33ce37d19e7258405d77d7..7694c8256d4c71ea1c36d1173ff7227325316a36 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 aa738da9a58cca796fabc3fdc10c0479e9b49f36..91ef8d40d1c07182e996102d2dfaa90a13a51118 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 f266bd2732c3ab839a83d8f6c0988455f60030da..4a9c74dac0dcca8297dd937d7780e1e6c3856a78 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 0000000000000000000000000000000000000000..df00753970632e3321611017c6e1ea4941378652 --- /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 effef12e1ba3d3b51ec9e1a60c8c2abdedfa6dde..9e58655da714164fbee7bdb22424f13951a93512 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 0000000000000000000000000000000000000000..f5c5e2c9b320df6b6fb6476f063352066497e773 --- /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 0000000000000000000000000000000000000000..d8dea384c349d664cb4d7dc6b3b1744f2686885b --- /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 5cc878eb29d0f8d7a50186f354cff32c8a7725ca..1d5a8fa5207d31308b40b1118b296e19f3e565f9 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 } ];