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>
|
||||
<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 ">
|
||||
|
||||
<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 ">
|
||||
<div class="flex flex-col pl-60 w-full min-h-screen ">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</v-app>
|
||||
|
||||
<size-indicator></size-indicator>
|
||||
@@ -25,69 +18,9 @@
|
||||
</template>
|
||||
|
||||
<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 {ref, onMounted, onUnmounted} from 'vue';
|
||||
import {useSideBarStore} from "@/stores/sideBarStore.js";
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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 {ref} from "vue";
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
|
||||
import {useUserProfileStore} from "@/stores/userProfileStore.js";
|
||||
|
||||
const {locale} = useI18n();
|
||||
const router = useRouter();
|
||||
const selectedLanguage = ref(locale.value);
|
||||
|
||||
const userProfileStore = useUserProfileStore()
|
||||
const creatorProfileStore = useCreatorProfileStore()
|
||||
|
||||
function initializeLocale() {
|
||||
const preferredLocale = localStorage.getItem('preferredLocale');
|
||||
selectedLanguage.value = preferredLocale === null ? locale.value : preferredLocale;
|
||||
@@ -28,72 +33,103 @@ initializeLocale();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="flex flex-col h-full overflow-y-auto custom-scrollbar bg-white">
|
||||
|
||||
<div class="flex justify-center py-3">
|
||||
|
||||
|
||||
<v-btn
|
||||
variant="text"
|
||||
class="p-0 mx-0 min-w-0"
|
||||
@click="toggleLanguage"
|
||||
>
|
||||
{{ selectedLanguage === 'fr' ? 'Fr' : 'En' }}
|
||||
</v-btn>
|
||||
<nav class="flex flex-col h-full bg-white">
|
||||
|
||||
<!-- APP SECTION -->
|
||||
<div class="px-4 mt-4">
|
||||
|
||||
<!-- LOGO HUTOPY -->
|
||||
<RouterLink class="d-none d-lg-flex" to="/">
|
||||
<v-img
|
||||
src="/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png"
|
||||
ref="popupButtonRef"
|
||||
alt="Hutopy Logo"
|
||||
class="mr-2 h-10 w-20"
|
||||
></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 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">
|
||||
<div class="font-bold"> {{ $t('sidebar.subscriptionTitle') }}</div>
|
||||
<div v-if="authStore.isAuthenticated" class="flex-grow px-5">
|
||||
<!-- LANGUAGE -->
|
||||
<v-btn
|
||||
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>
|
||||
|
||||
<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>
|
||||
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user