Combines Header and SideBar into the SideBar
This commit is contained in:
77
src/App.vue
77
src/App.vue
@@ -1,23 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app>
|
<v-app>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
|
||||||
<Header class=" w-full z-50 p-2 h-16"></Header>
|
<div class="fixed h-full w-60 border-r-2 z-30 ">
|
||||||
|
<side-bar></side-bar>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row ">
|
<div class="flex flex-col pl-60 w-full min-h-screen ">
|
||||||
|
|
||||||
<transition name="slide-fade">
|
|
||||||
<div v-show="sideBarStore.visible"
|
|
||||||
class="fixed h-full min-w-60 border-r-2 z-30 ">
|
|
||||||
<side-bar></side-bar>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<div class="flex flex-col w-full min-h-screen ">
|
|
||||||
<RouterView></RouterView>
|
<RouterView></RouterView>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</v-app>
|
</v-app>
|
||||||
|
|
||||||
<size-indicator></size-indicator>
|
<size-indicator></size-indicator>
|
||||||
@@ -25,69 +18,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script async setup>
|
<script async setup>
|
||||||
import Header from "@/views/main/Header.vue";
|
|
||||||
import Footer from "@/views/main/Footer.vue";
|
|
||||||
import SideBar from "@/views/main/SideBar.vue";
|
import SideBar from "@/views/main/SideBar.vue";
|
||||||
import {ref, onMounted, onUnmounted} from 'vue';
|
|
||||||
import {useSideBarStore} from "@/stores/sideBarStore.js";
|
|
||||||
import SizeIndicator from "@/views/tools/SizeIndicator.vue";
|
import SizeIndicator from "@/views/tools/SizeIndicator.vue";
|
||||||
|
|
||||||
const showPopup = ref(false);
|
|
||||||
const popup = ref(null);
|
|
||||||
const popupButton = ref(null);
|
|
||||||
let closeSidebarTimer = null;
|
|
||||||
|
|
||||||
const sideBarStore = useSideBarStore()
|
|
||||||
|
|
||||||
const openSidebar = () => {
|
|
||||||
clearCloseSidebarTimer();
|
|
||||||
sideBarStore.show()
|
|
||||||
};
|
|
||||||
|
|
||||||
const startCloseSidebarTimer = () => {
|
|
||||||
closeSidebarTimer = setTimeout(() => {
|
|
||||||
sideBarStore.hide()
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearCloseSidebarTimer = () => {
|
|
||||||
clearTimeout(closeSidebarTimer);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (
|
|
||||||
popup.value &&
|
|
||||||
!popup.value.contains(event.target) &&
|
|
||||||
!popupButton.value.contains(event.target) &&
|
|
||||||
!event.target.closest('.bg-purple')
|
|
||||||
) {
|
|
||||||
showPopup.value = false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!event.target.closest('.w-48') &&
|
|
||||||
!event.target.closest('.v-app-bar-nav-icon')
|
|
||||||
) {
|
|
||||||
sideBarStore.hide()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener('click', handleClickOutside);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('click', handleClickOutside);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Do not remove. Used for animation */
|
|
||||||
.slide-fade-enter-active, .slide-fade-leave-active {
|
|
||||||
transition: transform 0.7s ease, opacity 0.7s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-fade-enter, .slide-fade-leave-to {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,241 +0,0 @@
|
|||||||
<template>
|
|
||||||
<header
|
|
||||||
class="py-1 flex items-center justify-between bg-white shadow-md shadow-gray-700 z-20"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<v-app-bar-nav-icon @click.stop="sideBarStore.toggle()">
|
|
||||||
</v-app-bar-nav-icon>
|
|
||||||
|
|
||||||
<RouterLink class="d-sm-block d-md-block d-lg-none" to="/">
|
|
||||||
|
|
||||||
<v-img
|
|
||||||
src="/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png"
|
|
||||||
ref="popupButtonRef"
|
|
||||||
alt="Hutopy Logo"
|
|
||||||
class="w-10"
|
|
||||||
></v-img>
|
|
||||||
</RouterLink>
|
|
||||||
|
|
||||||
<RouterLink class="d-none d-lg-flex" to="/">
|
|
||||||
<v-img
|
|
||||||
src="/medias/hutopy.png"
|
|
||||||
ref="popupButtonRef"
|
|
||||||
alt="Hutopy Logo"
|
|
||||||
class="mr-2 h-10 w-20"
|
|
||||||
></v-img>
|
|
||||||
<div class="flex align-center text-sm">ALPHA</div>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
<!-- SmallScreen Searchbar -->
|
|
||||||
<div class="flex-grow flex items-center justify-center relative">
|
|
||||||
<div v-if="showSearch && isSmallScreen" class="search-bar-wrapper">
|
|
||||||
<v-text-field
|
|
||||||
density="compact"
|
|
||||||
rounded
|
|
||||||
variant="outlined"
|
|
||||||
v-model="searchQuery"
|
|
||||||
placeholder="Recherche"
|
|
||||||
hide-details
|
|
||||||
clearable
|
|
||||||
append-inner-icon="mdi-magnify"
|
|
||||||
@click.stop
|
|
||||||
@click:append-inner="onSearch"
|
|
||||||
@keyup.enter="onSearch"
|
|
||||||
>
|
|
||||||
</v-text-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- largeScreen Searchbar -->
|
|
||||||
<div v-if="showSearch && !isSmallScreen" class="search-bar-large-screen">
|
|
||||||
<v-text-field
|
|
||||||
density="compact"
|
|
||||||
rounded
|
|
||||||
variant="outlined"
|
|
||||||
v-model="searchQuery"
|
|
||||||
placeholder="Recherche"
|
|
||||||
hide-details
|
|
||||||
clearable
|
|
||||||
append-inner-icon="mdi-magnify"
|
|
||||||
@click.stop
|
|
||||||
@click:append-inner="onSearch"
|
|
||||||
@keyup.enter="onSearch"
|
|
||||||
>
|
|
||||||
</v-text-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center space-x-4 search-container">
|
|
||||||
<template v-if="!showSearch">
|
|
||||||
<v-icon class="mx-2 cursor-pointer" @click.stop="toggleSearch">mdi-magnify</v-icon>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<v-menu open-on-click>
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<div v-bind="props" class="flex align-center font-sans py-1 px-2 rounded-lg hover:bg-gray-100">
|
|
||||||
<span class="max-w-xs hidden md:block capitalize">
|
|
||||||
{{ userProfileStore.alias }}
|
|
||||||
</span>
|
|
||||||
<img
|
|
||||||
:src="userProfileStore.portraitUrl"
|
|
||||||
alt="Profile Image"
|
|
||||||
class="ml-2 rounded-full"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-list min-width="200px" class="align-center mt-3 left-3">
|
|
||||||
<template v-if="!authStore.isAuthenticated">
|
|
||||||
<v-list-item class="nav-button">
|
|
||||||
<v-list-item-title>
|
|
||||||
<v-btn to="/login" class="w-100" variant="plain">Connexion</v-btn>
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<v-list-item v-if="creatorProfileStore.creator.name && Object.keys(creatorProfileStore.creator).length > 0" class="nav-button">
|
|
||||||
<router-link :to="`/@${creatorProfileStore.creator.name}`">
|
|
||||||
<v-btn class="w-100" variant="plain">{{ creatorProfileStore.creator.name }}</v-btn>
|
|
||||||
</router-link>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item v-if="!creatorProfileStore.hasCreator" class="nav-button">
|
|
||||||
<router-link to="/profile?target=CreatorPage">
|
|
||||||
<v-btn class="w-100" variant="plain">Activer votre page</v-btn>
|
|
||||||
</router-link>
|
|
||||||
</v-list-item>
|
|
||||||
|
|
||||||
<v-list-item class="nav-button">
|
|
||||||
<v-list-item-title>
|
|
||||||
<v-btn to="/profile" class="w-100" variant="plain">{{ $t('header.myprofile') }}</v-btn>
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item class="nav-button">
|
|
||||||
<v-list-item-title>
|
|
||||||
<v-btn to="/wallet" class="w-100" variant="plain">{{ $t('header.wallet') }}</v-btn>
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item class="nav-button">
|
|
||||||
<v-list-item-title>
|
|
||||||
<v-btn @click="authStore.logout" class="w-100" variant="plain">{{ $t('header.Signout') }}</v-btn>
|
|
||||||
</v-list-item-title>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onBeforeUnmount, onBeforeMount, watch } from "vue";
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useSideBarStore } from '@/stores/sideBarStore.js';
|
|
||||||
|
|
||||||
import { useAuthStore } from "@/stores/authStore.js";
|
|
||||||
import { useDisplay } from 'vuetify';
|
|
||||||
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
|
||||||
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const userProfileStore = useUserProfileStore();
|
|
||||||
const creatorProfileStore = useCreatorProfileStore();
|
|
||||||
|
|
||||||
const sideBarStore = useSideBarStore();
|
|
||||||
const { smAndDown } = useDisplay();
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const searchQuery = ref("");
|
|
||||||
const showSearch = ref(false);
|
|
||||||
const isSmallScreen = ref(smAndDown.value);
|
|
||||||
|
|
||||||
watch(smAndDown, (val) => {
|
|
||||||
isSmallScreen.value = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSearch = () => {
|
|
||||||
const query = searchQuery.value.trim();
|
|
||||||
if (!query) {
|
|
||||||
router.push("/browse");
|
|
||||||
} else {
|
|
||||||
const words = query.split(" ");
|
|
||||||
if (words.length === 1) {
|
|
||||||
router.push(`/@${words[0]}`);
|
|
||||||
} else {
|
|
||||||
router.push({ name: "browse", query: { q: query } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSearch = () => {
|
|
||||||
showSearch.value = !showSearch.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (!event.target.closest('.search-container')) {
|
|
||||||
showSearch.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeMount(() => {
|
|
||||||
document.addEventListener('click', handleClickOutside)
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
document.removeEventListener('click', handleClickOutside);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.search-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-button:hover {
|
|
||||||
@apply bg-[#903175] text-gray-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.absolute-center {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 700px;
|
|
||||||
z-index: 50;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-large-screen {
|
|
||||||
display: flex;
|
|
||||||
width: 600px;
|
|
||||||
z-index: 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.search-bar-large-screen {
|
|
||||||
display: flex;
|
|
||||||
margin-left: auto;
|
|
||||||
width: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.absolute-center {
|
|
||||||
left: 65%;
|
|
||||||
transform: translateX(-55%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -4,11 +4,16 @@ import {useAuthStore} from "@/stores/authStore.js";
|
|||||||
import {useRouter} from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {useI18n} from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
|
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
|
||||||
|
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
||||||
|
|
||||||
const {locale} = useI18n();
|
const {locale} = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const selectedLanguage = ref(locale.value);
|
const selectedLanguage = ref(locale.value);
|
||||||
|
|
||||||
|
const userProfileStore = useUserProfileStore()
|
||||||
|
const creatorProfileStore = useCreatorProfileStore()
|
||||||
|
|
||||||
function initializeLocale() {
|
function initializeLocale() {
|
||||||
const preferredLocale = localStorage.getItem('preferredLocale');
|
const preferredLocale = localStorage.getItem('preferredLocale');
|
||||||
selectedLanguage.value = preferredLocale === null ? locale.value : preferredLocale;
|
selectedLanguage.value = preferredLocale === null ? locale.value : preferredLocale;
|
||||||
@@ -28,72 +33,103 @@ initializeLocale();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="flex flex-col h-full overflow-y-auto custom-scrollbar bg-white">
|
<nav class="flex flex-col h-full bg-white">
|
||||||
|
|
||||||
<div class="flex justify-center py-3">
|
<!-- APP SECTION -->
|
||||||
|
<div class="px-4 mt-4">
|
||||||
|
|
||||||
|
<!-- LOGO HUTOPY -->
|
||||||
<v-btn
|
<RouterLink class="d-none d-lg-flex" to="/">
|
||||||
variant="text"
|
<v-img
|
||||||
class="p-0 mx-0 min-w-0"
|
src="/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png"
|
||||||
@click="toggleLanguage"
|
ref="popupButtonRef"
|
||||||
>
|
alt="Hutopy Logo"
|
||||||
{{ selectedLanguage === 'fr' ? 'Fr' : 'En' }}
|
class="mr-2 h-10 w-20"
|
||||||
</v-btn>
|
></v-img>
|
||||||
|
</RouterLink>
|
||||||
|
<!-- YOUR PAGE -->
|
||||||
|
<router-link v-if="creatorProfileStore.hasCreator"
|
||||||
|
:to="`/@${creatorProfileStore.creator.name}`">
|
||||||
|
<v-btn class="w-full"
|
||||||
|
prepend-icon="mdi-home-account"
|
||||||
|
variant="tonal">
|
||||||
|
{{ creatorProfileStore.creator.name }}
|
||||||
|
</v-btn>
|
||||||
|
</router-link>
|
||||||
|
<router-link v-else-if="authStore.isAuthenticated"
|
||||||
|
class="nav-button"
|
||||||
|
to="/profile?target=CreatorPage">
|
||||||
|
<v-btn class="w-100" variant="plain">Activer votre page</v-btn>
|
||||||
|
</router-link>
|
||||||
|
<!-- CREATE CONTENT -->
|
||||||
|
</div>
|
||||||
|
<!-- SUBSCRIPTION SECTION -->
|
||||||
|
<div class="flex-grow px-4">
|
||||||
|
<template v-if="authStore.isAuthenticated">
|
||||||
|
<div class="font-bold"> {{ $t('sidebar.subscriptionTitle') }}</div>
|
||||||
|
<!-- SUBSCRIPTION LIST -->
|
||||||
|
<subscription-list>
|
||||||
|
</subscription-list>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-solid border-1"></div>
|
|
||||||
|
|
||||||
|
<!-- USER SECTION -->
|
||||||
|
<div class="px-4 mb-4">
|
||||||
|
<div class="flex justify-center p-2">
|
||||||
|
<img
|
||||||
|
:src="userProfileStore.portraitUrl"
|
||||||
|
alt="Profile Image"
|
||||||
|
class="rounded-full"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
>
|
||||||
|
<span class="ml-2 text-lg font-sans capitalize">
|
||||||
|
{{ userProfileStore.alias }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column gap-4">
|
||||||
|
<!-- PROFILE -->
|
||||||
|
<v-btn to="/profile"
|
||||||
|
class="w-full"
|
||||||
|
prepend-icon="mdi-account"
|
||||||
|
variant="tonal">
|
||||||
|
{{ $t('header.myprofile') }}
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
<div v-if="authStore.isAuthenticated" class="flex-grow px-5 py-4">
|
<!-- LANGUAGE -->
|
||||||
<div class="font-bold"> {{ $t('sidebar.subscriptionTitle') }}</div>
|
<v-btn
|
||||||
<div v-if="authStore.isAuthenticated" class="flex-grow px-5">
|
variant="tonal"
|
||||||
|
class="w-full"
|
||||||
|
prepend-icon="mdi-translate-variant"
|
||||||
|
@click="toggleLanguage"
|
||||||
|
>
|
||||||
|
{{ selectedLanguage === 'fr' ? 'Fr' : 'En' }}
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- LOGIN / LOGOUT -->
|
||||||
|
<template v-if="!authStore.isAuthenticated">
|
||||||
|
<v-btn to="/login"
|
||||||
|
variant="tonal"
|
||||||
|
class="w-full"
|
||||||
|
prepend-icon="mdi-login">
|
||||||
|
Connexion
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-btn @click="authStore.logout"
|
||||||
|
variant="tonal"
|
||||||
|
class="w-full"
|
||||||
|
prepend-icon="mdi-logout">
|
||||||
|
{{ $t('header.Signout') }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<subscription-list>
|
|
||||||
</subscription-list>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="flex-grow px-5 py-4 flex justify-center font-bold ">
|
|
||||||
<span>Connectez-vous</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
nav {
|
|
||||||
overflow-y: auto;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Firefox */
|
|
||||||
.custom-scrollbar {
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: #903175 #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chrome, Safari) */
|
|
||||||
.custom-scrollbar::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-track {
|
|
||||||
background: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #903175;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 2px solid #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
@apply font-sans font-bold ml-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
|
||||||
@apply relative;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user