aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/bootstrap/components/Header.vue
blob: dfdd66719e3b0d5ce4716761b06977d0d1aed197 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<!-- eslint-disable vue/max-attributes-per-line -->
<template>
  <header class="sticky top-0 left-0 z-40">
    <div class="flex header-container">
      <div id="header-mobile-menu" ref="sideMenu" class="side-menu" :style="sideMenuStyle">
        <div class="pt-4 pl-4 pr-6">
          <nav id="header-mobile-nav" class="relative flex flex-col pr-3">
            <div v-for="route in routes" :key="route.path" class="m-auto ml-0">
              <div class="my-1" @click="closeSideMenu">
                <router-link :to="route">
                  {{ route.name }}
                </router-link>
              </div>
            </div>
          </nav>
        </div>
      </div>
      <div class="flex flex-row w-full md:mx-3">
        <div class="hidden w-4 lg:block" />
        <div class="flex px-4 my-auto text-xl md:hidden">
          <div v-if="!sideMenuActive" class="w-7" @click.prevent="openSideMenu">
            <fa-icon icon="bars" />
          </div>
          <div v-else class="text-2xl w-7">
            <fa-icon icon="times" />
          </div>
        </div>
        <div id="site-title-container" class="flex m-0 mr-3">
          <div class="inline-block px-1">
            <slot name="site_logo" />
          </div>
          <div id="site-title" class="inline-block m-auto mx-1">
            <router-link to="/">
              <h3>{{ siteTitle }}</h3>
            </router-link>
          </div>
        </div>
        <div class="hidden w-4 lg:block" />
        <nav id="header-desktop-nav" class="flex-row hidden mr-2 md:flex">
          <span v-for="route in routes" :key="route.fullPath" class="flex px-1 lg:px-3">
            <div v-if="!route.hide" class="m-auto">
              <router-link :to="route" class="flex-auto">
                {{ route.name }}
              </router-link>
            </div>
          </span>
        </nav>
        <div id="user-menu" ref="userMenu" class="drop-controller" :class="{ 'hovered': userMenuHovered }">
          <div class="user-menu">
            Hello <span class="font-semibold">{{ uname }}</span>
          </div>
          <div ref="userDrop" class="absolute top-0 right-0 duration-100 ease-in-out" style="z-index:-1" :style="dropStyle">
            <div class="drop-menu" @click.prevent="userMenuHovered = false">
              <span class="space-x-2" />
              <a v-if="!loggedIn" href="#" data-header-dropdown="register" @click="gotoRoute('/register')">
                Register
              </a>
              <a v-else href="#" data-header-dropdown="account" @click="gotoRoute('/account')">
                Account
              </a>
              <a v-if="!loggedIn" href="#" data-header-dropdown="login" @click="gotoRoute('/login')">
                Login
              </a>
              <a v-else href="#" data-header-dropdown="logout" @click.prevent="OnLogout">
                Logout
              </a>
            </div>
          </div>
        </div>
        <div class="hidden space-x-4 lg:block" />
      </div>
    </div>
  </header>
</template>

<script setup lang="ts">

import { debounce, find } from 'lodash'
import { useElementSize, onClickOutside, useElementHover } from '@vueuse/core'
import { computed, ref, toRefs } from 'vue'
import { useSession, useUser, useEnvSize, apiCall } from '@vnuge/vnlib.browser'
import { RouteLocation, useRouter } from 'vue-router';

const props = defineProps<{
  siteTitle: string,
  routes: RouteLocation[]
}>()

const { siteTitle, routes } = toRefs(props)

const { loggedIn } = useSession()
const { userName, logout } = useUser()
const { headerHeight } = useEnvSize()

//Get the router for navigation
const router = useRouter()

const sideMenuActive = ref(false)

const userDrop = ref(null)
const sideMenu = ref(null)
const userMenu = ref(null)

const dropMenuSize = useElementSize(userDrop)
const sideMenuSize = useElementSize(sideMenu)
const userMenuHovered = useElementHover(userMenu)

const uname = computed(() => userName.value || 'Visitor')
const sideMenuStyle = computed(() => {
  // Side menu should be the exact height of the page and under the header,
  // So menu height is the height of the page minus the height of the header
  return {
    height: `calc(100vh - ${headerHeight.value}px)`,
    left: sideMenuActive.value ? '0' : `-${sideMenuSize.width.value}px`,
    top: `${headerHeight.value}px`
  }
})

const dropStyle = computed(() => {
  return {
    'margin-top': userMenuHovered.value ? `${headerHeight.value}px` : `-${(dropMenuSize.height.value - headerHeight.value)}px`
  }
})

const closeSideMenu = debounce(() => sideMenuActive.value = false, 50)
const openSideMenu = debounce(() => sideMenuActive.value = true, 50)

//Close side menu when clicking outside of it
onClickOutside(sideMenu, closeSideMenu)

//Redirect to the route when clicking on it
const gotoRoute = (route : string) =>{

  //Get all routes from the router
  const allRoutes = router.getRoutes();

  //Try to find the route by its path
  const goto = find(allRoutes, { path: route });

  if(goto){
    //navigate to the route manually
    router.push(goto);
  }
  else{
    //Fallback to full navigation
    window.location.assign(route);
  }
}

const OnLogout = () =>{
  apiCall(async ({ toaster }) => {
    await logout()
    toaster.general.success({
      id: 'logout-success',
      title: 'Success',
      text: 'You have been logged out',
      duration: 5000
    })
  })
}

</script>