User dropdown

User id in acquisition
about
This commit is contained in:
2024-05-16 10:41:12 +02:00
parent 0e4f1103d4
commit 6d67e92749
10 changed files with 105 additions and 13 deletions

View File

@@ -1,14 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from '@/auth'; import { useAuthStore } from '@/auth';
import { RouterView } from 'vue-router'; import { computed } from 'vue';
import { RouterView, useRoute } from 'vue-router';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
const route = useRoute();
const authStore = useAuthStore(); const authStore = useAuthStore();
const hideSidebar = computed(() => {
return !authStore.isLoggedIn || route.name === 'callback' || route.name === 'about';
});
</script> </script>
<template> <template>
<template v-if="!authStore.isLoggedIn"> <template v-if="hideSidebar">
<RouterView /> <RouterView />
</template> </template>
<template v-else> <template v-else>

View File

@@ -18,6 +18,7 @@ export const useAuthStore = defineStore('auth', () => {
const isLoggedIn = computed(() => !!user.value); const isLoggedIn = computed(() => !!user.value);
const accessToken = computed(() => user.value?.access_token); const accessToken = computed(() => user.value?.access_token);
const username = computed(() => user.value?.profile.name ?? ""); const username = computed(() => user.value?.profile.name ?? "");
const userId = computed(() => user.value?.profile.sub ?? "");
const redirect = async () => { const redirect = async () => {
await userManager.signinRedirect(); await userManager.signinRedirect();
@@ -42,5 +43,5 @@ export const useAuthStore = defineStore('auth', () => {
} }
userManager.events.addUserLoaded(setUser); userManager.events.addUserLoaded(setUser);
userManager.getUser().then(setUser); userManager.getUser().then(setUser);
return { redirect, login, logout, isLoggedIn, accessToken, username }; return { redirect, login, logout, isLoggedIn, accessToken, username, userId };
}); });

View File

@@ -0,0 +1,46 @@
<script setup lang="ts">
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/outline';
import { vOnClickOutside } from '@vueuse/components';
import { useEventListener } from '@vueuse/core';
import { ref } from 'vue';
const isOpen = ref(false);
useEventListener('keyup', e => {
if (e.key === 'Escape') {
isOpen.value = false;
}
});
</script>
<template>
<div class="dropdown" :class="{'dropdown-open': isOpen, 'dropdown-close': !isOpen}" v-on-click-outside="() => isOpen = false">
<button @click="isOpen = !isOpen">
<slot name="button" />
<ChevronDownIcon v-if="!isOpen" class="chevron" />
<ChevronUpIcon v-else class="chevron" />
</button>
<Transition name="fade">
<div v-if="isOpen" class="relative">
<div class="z-10 divide-y rounded-b-md absolute">
<slot />
</div>
</div>
</Transition>
</div>
</template>
<style scoped lang="postcss">
.chevron {
@apply w-4 h-4 ms-1;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 100ms ease-out;
}
</style>

View File

@@ -1,4 +1,5 @@
export { default as ClipboardButton } from './ClipboardButton.vue'; export { default as ClipboardButton } from './ClipboardButton.vue';
export { default as Dropdown } from './Dropdown.vue';
export { default as LoadingSpinner } from './LoadingSpinner.vue'; export { default as LoadingSpinner } from './LoadingSpinner.vue';
export { default as Modal } from './Modal.vue'; export { default as Modal } from './Modal.vue';
export { default as SliderCheckbox } from './SliderCheckbox.vue'; export { default as SliderCheckbox } from './SliderCheckbox.vue';

View File

@@ -36,7 +36,7 @@ defineEmits<Emits>();
const marketTaxStore = useMarketTaxStore(); const marketTaxStore = useMarketTaxStore();
const threshold = useStorage('market-aquisition-threshold', 10); const threshold = useStorage('market-acquisition-threshold', 10);
const filter = ref(""); const filter = ref("");
const { sortedArray, headerProps } = useSort<Result>(computed(() => props.items const { sortedArray, headerProps } = useSort<Result>(computed(() => props.items
.filter(r => r.type.name.toLowerCase().includes(filter.value.toLowerCase())) .filter(r => r.type.name.toLowerCase().includes(filter.value.toLowerCase()))

View File

@@ -1,3 +1,4 @@
import { useAuthStore } from "@/auth";
import { marbasAxiosInstance } from "@/service"; import { marbasAxiosInstance } from "@/service";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
@@ -17,6 +18,7 @@ const endpoint = '/api/acquisitions';
export const useAcquiredItemStore = defineStore('market-acquisition', () => { export const useAcquiredItemStore = defineStore('market-acquisition', () => {
const _acquiredItems = ref<AcquiredMarketItem[]>([]); const _acquiredItems = ref<AcquiredMarketItem[]>([]);
const authStore = useAuthStore();
const items = computed(() => _acquiredItems.value); const items = computed(() => _acquiredItems.value);
const addAcquiredItem = async (type: number, quantity: number, price: number) => { const addAcquiredItem = async (type: number, quantity: number, price: number) => {
@@ -27,7 +29,7 @@ export const useAcquiredItemStore = defineStore('market-acquisition', () => {
price: price, price: price,
date: new Date(), date: new Date(),
source: 'bo', source: 'bo',
user: 0 // TODO: get user id user: authStore.userId,
})).data]; })).data];
}; };
const removeAcquiredItem = async (type: number, quantity: number) => { const removeAcquiredItem = async (type: number, quantity: number) => {

View File

@@ -96,7 +96,7 @@ watchEffect(async () => {
<template> <template>
<div @click="() => isOpen = true" v-on-click-outside="() => isOpen = false"> <div @click="() => isOpen = true" v-on-click-outside="() => isOpen = false">
<div class="fake-input"> <div class="fake-input">
<img v-if="value?.id" :src="`https://images.evetech.net/types/${value.id}/icon`" /> <img v-if="value?.id" :src="`https://images.evetech.net/types/${value.id}/icon`" alt="" />
<input type="text" v-model="name" @keyup.enter="submit" @keyup.down="moveDown" @keyup.up="moveUp" /> <input type="text" v-model="name" @keyup.enter="submit" @keyup.down="moveDown" @keyup.up="moveUp" />
</div> </div>
<div v-if="suggestions.length > 1" class="z-10 absolute w-96"> <div v-if="suggestions.length > 1" class="z-10 absolute w-96">

View File

@@ -11,5 +11,5 @@ export const routes: RouteRecordRaw[] = [
{ path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue') }, { path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue') },
] }, ] },
{ path: '/tools', component: () => import('@/pages/Tools.vue') }, { path: '/tools', component: () => import('@/pages/Tools.vue') },
{ path: '/about', component: () => import('@/pages/About.vue') }, { path: '/about', name: 'about', component: () => import('@/pages/About.vue') },
]; ];

View File

@@ -44,7 +44,13 @@ marbasAxiosInstance.interceptors.response.use(async r => {
return r; return r;
}) })
marbasAxiosInstance.interceptors.request.use(r => { marbasAxiosInstance.interceptors.request.use(r => {
const accessToken = useAuthStore().accessToken; const authStore = useAuthStore();
if (!authStore.isLoggedIn) {
throw new Error("Not logged in");
}
const accessToken = authStore.accessToken;
if (accessToken) { if (accessToken) {
r.headers.Authorization = `Bearer ${accessToken}`; r.headers.Authorization = `Bearer ${accessToken}`;

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from '@/auth'; import { useAuthStore } from '@/auth';
import { Dropdown } from '@/components';
import { RouterLink } from 'vue-router'; import { RouterLink } from 'vue-router';
const links = [ const links = [
@@ -18,22 +19,52 @@ const logout = async () => {
<template> <template>
<aside class="fixed top-0 left-0 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0"> <aside class="fixed top-0 left-0 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0">
<div class="h-full px-3 py-4 overflow-y-auto bg-slate-700 flex flex-col"> <div class="h-full px-3 py-4 overflow-y-auto bg-slate-700 flex flex-col">
<div class="mb-2 border-b-2 border-emerald-500">
<Dropdown class="mb-2 user-dropdown">
<template #button>
<span>{{ authStore.username }}</span>
</template>
<ul>
<li>
<RouterLink class="sidebar-button py-0.5 px-2" :to="{name: 'about'}">About EVE Online</RouterLink>
</li>
<li>
<a class="sidebar-button py-0.5 px-2 text-amber-700" @click="logout">Logout</a>
</li>
</ul>
</Dropdown>
</div>
<ul class="space-y-2 font-medium"> <ul class="space-y-2 font-medium">
<li v-for="link in links" :key="link.name"> <li v-for="link in links" :key="link.name">
<RouterLink :to="link.path" class="flex items-center p-2 rounded-md hover:bg-slate-800"> <RouterLink :to="link.path" class="sidebar-button p-2">
<span>{{ link.name }}</span> <span>{{ link.name }}</span>
</RouterLink> </RouterLink>
</li> </li>
</ul> </ul>
<div class="mt-auto">
<button @click="logout">Logout</button>
</div>
</div> </div>
</aside> </aside>
</template> </template>
<style scoped lang="postcss"> <style scoped lang="postcss">
.sidebar-button {
@apply flex items-center rounded-md hover:bg-slate-800 cursor-pointer;
}
.router-link-active { .router-link-active {
@apply bg-emerald-500 hover:bg-emerald-700; @apply bg-emerald-500 hover:bg-emerald-700;
} }
.user-dropdown {
@apply w-full;
:deep(>div) {
@apply w-full;
>div {
@apply w-full bg-slate-800;
}
}
:deep(>button) {
@apply bg-slate-700 hover:bg-slate-800 border-none flex items-center w-full;
}
&.dropdown-open:deep(>button) {
@apply bg-slate-800 rounded-b-none;
}
}
</style> </style>