Client - add dropdown to select language
This commit is contained in:
		@@ -1,2 +0,0 @@
 | 
				
			|||||||
VUE_APP_I18N_LOCALE=en
 | 
					 | 
				
			||||||
VUE_APP_I18N_FALLBACK_LOCALE=en
 | 
					 | 
				
			||||||
							
								
								
									
										97
									
								
								fittrackee_client/src/components/Common/Dropdown.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								fittrackee_client/src/components/Common/Dropdown.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="dropdown-wrapper">
 | 
				
			||||||
 | 
					    <div class="dropdown-selected" @click="openDropdown">
 | 
				
			||||||
 | 
					      <slot></slot>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <ul class="dropdown-list" v-if="isOpen">
 | 
				
			||||||
 | 
					      <li
 | 
				
			||||||
 | 
					        class="dropdown-item"
 | 
				
			||||||
 | 
					        :class="{ selected: option.value === selected }"
 | 
				
			||||||
 | 
					        v-for="(option, index) in dropdownOptions"
 | 
				
			||||||
 | 
					        :key="index"
 | 
				
			||||||
 | 
					        @click="updateSelected(option)"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {{ option.label }}
 | 
				
			||||||
 | 
					      </li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import { PropType, defineComponent, ref } from 'vue'
 | 
				
			||||||
 | 
					  import { IDropdownOption, TDropdownOptions } from '@/types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default defineComponent({
 | 
				
			||||||
 | 
					    name: 'Dropdown',
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					      options: {
 | 
				
			||||||
 | 
					        type: Object as PropType<TDropdownOptions>,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      selected: {
 | 
				
			||||||
 | 
					        type: String,
 | 
				
			||||||
 | 
					        required: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    emits: {
 | 
				
			||||||
 | 
					      selected: (option: IDropdownOption) => option,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    setup(props, { emit }) {
 | 
				
			||||||
 | 
					      let isOpen = ref(false)
 | 
				
			||||||
 | 
					      let dropdownOptions = props.options.map((option) => option)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      function openDropdown() {
 | 
				
			||||||
 | 
					        isOpen.value = true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      function updateSelected(option: IDropdownOption) {
 | 
				
			||||||
 | 
					        emit('selected', option)
 | 
				
			||||||
 | 
					        isOpen.value = false
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      function getSelectedLabel(selectedValue: string) {
 | 
				
			||||||
 | 
					        return props.options.filter(
 | 
				
			||||||
 | 
					          (option: IDropdownOption) => option.value === selectedValue
 | 
				
			||||||
 | 
					        )[0].label
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        dropdownOptions,
 | 
				
			||||||
 | 
					        updateSelected,
 | 
				
			||||||
 | 
					        getSelectedLabel,
 | 
				
			||||||
 | 
					        isOpen,
 | 
				
			||||||
 | 
					        openDropdown,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					  .dropdown-list {
 | 
				
			||||||
 | 
					    list-style-type: none;
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    margin: 5px 0;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    border: solid 1px lightgrey;
 | 
				
			||||||
 | 
					    box-shadow: 2px 2px 5px lightgrey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    li {
 | 
				
			||||||
 | 
					      padding-top: 5px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    li:last-child {
 | 
				
			||||||
 | 
					      padding-bottom: 5px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .dropdown-item {
 | 
				
			||||||
 | 
					    cursor: default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.selected {
 | 
				
			||||||
 | 
					      font-weight: bold;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.selected::after {
 | 
				
			||||||
 | 
					      content: ' ✔';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -2,14 +2,14 @@
 | 
				
			|||||||
  <div id="nav">
 | 
					  <div id="nav">
 | 
				
			||||||
    <div class="container">
 | 
					    <div class="container">
 | 
				
			||||||
      <div class="nav-app-name">
 | 
					      <div class="nav-app-name">
 | 
				
			||||||
        <span class="nav-item app-name">FitTrackee</span>
 | 
					        <div class="nav-item app-name">FitTrackee</div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="nav-icon-open" :class="{ 'menu-open': isMenuOpen }">
 | 
					      <div class="nav-icon-open" :class="{ 'menu-open': isMenuOpen }">
 | 
				
			||||||
        <i class="fa fa-bars hamburger-icon" @click="openMenu()"></i>
 | 
					        <i class="fa fa-bars hamburger-icon" @click="openMenu()"></i>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="nav-items" :class="{ 'menu-open': isMenuOpen }">
 | 
					      <div class="nav-items" :class="{ 'menu-open': isMenuOpen }">
 | 
				
			||||||
        <div class="nav-items-close">
 | 
					        <div class="nav-items-close">
 | 
				
			||||||
          <span class="app-name">FitTrackee</span>
 | 
					          <div class="app-name">FitTrackee</div>
 | 
				
			||||||
          <i
 | 
					          <i
 | 
				
			||||||
            class="fa fa-close close-icon nav-item"
 | 
					            class="fa fa-close close-icon nav-item"
 | 
				
			||||||
            :class="{ 'menu-closed': !isMenuOpen }"
 | 
					            :class="{ 'menu-closed': !isMenuOpen }"
 | 
				
			||||||
@@ -20,16 +20,25 @@
 | 
				
			|||||||
          <router-link class="nav-item" to="/">{{
 | 
					          <router-link class="nav-item" to="/">{{
 | 
				
			||||||
            t('dashboard.DASHBOARD')
 | 
					            t('dashboard.DASHBOARD')
 | 
				
			||||||
          }}</router-link>
 | 
					          }}</router-link>
 | 
				
			||||||
          <span class="nav-item">{{ t('workouts.WORKOUTS') }}</span>
 | 
					          <div class="nav-item">{{ t('workouts.WORKOUTS') }}</div>
 | 
				
			||||||
          <span class="nav-item">{{ t('statistics.STATISTICS') }}</span>
 | 
					          <div class="nav-item">{{ t('statistics.STATISTICS') }}</div>
 | 
				
			||||||
          <span class="nav-item">{{ t('administration.ADMIN') }}</span>
 | 
					          <div class="nav-item">{{ t('administration.ADMIN') }}</div>
 | 
				
			||||||
          <span class="nav-item">{{ t('workouts.ADD_WORKOUT') }}</span>
 | 
					          <div class="nav-item">{{ t('workouts.ADD_WORKOUT') }}</div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="nav-items-user-menu">
 | 
					        <div class="nav-items-user-menu">
 | 
				
			||||||
          <span class="nav-item">User</span>
 | 
					          <div class="nav-item">User</div>
 | 
				
			||||||
          <span class="nav-item">{{ t('user.LOGOUT') }}</span>
 | 
					          <div class="nav-item">{{ t('user.LOGOUT') }}</div>
 | 
				
			||||||
          <span class="nav-item">{{ t('user.REGISTER') }}</span>
 | 
					          <!--          <span class="nav-item">{{ t('user.REGISTER') }}</span>-->
 | 
				
			||||||
          <span class="nav-item">{{ t('user.LOGIN') }}</span>
 | 
					          <!--          <span class="nav-item">{{ t('user.LOGIN') }}</span>-->
 | 
				
			||||||
 | 
					          <Dropdown
 | 
				
			||||||
 | 
					            v-if="availableLanguages && language"
 | 
				
			||||||
 | 
					            class="nav-item"
 | 
				
			||||||
 | 
					            :options="availableLanguages"
 | 
				
			||||||
 | 
					            :selected="language"
 | 
				
			||||||
 | 
					            @selected="updateLanguage"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <i class="fa fa-language"></i>
 | 
				
			||||||
 | 
					          </Dropdown>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -37,21 +46,45 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { defineComponent, ref } from 'vue'
 | 
					  import { computed, defineComponent, ref } from 'vue'
 | 
				
			||||||
  import { useI18n } from 'vue-i18n'
 | 
					  import { useI18n } from 'vue-i18n'
 | 
				
			||||||
 | 
					  import { useStore } from 'vuex'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  import { IDropdownOption } from '@/types'
 | 
				
			||||||
 | 
					  import Dropdown from '@/components/Common/Dropdown.vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export default defineComponent({
 | 
					  export default defineComponent({
 | 
				
			||||||
    name: 'NavBar',
 | 
					    name: 'NavBar',
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      Dropdown,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    setup() {
 | 
					    setup() {
 | 
				
			||||||
 | 
					      const { t, locale, availableLocales } = useI18n()
 | 
				
			||||||
 | 
					      const store = useStore()
 | 
				
			||||||
 | 
					      const availableLanguages = availableLocales.map((l) => {
 | 
				
			||||||
 | 
					        return { label: l.toUpperCase(), value: l }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
      let isMenuOpen = ref(false)
 | 
					      let isMenuOpen = ref(false)
 | 
				
			||||||
      const { t } = useI18n()
 | 
					 | 
				
			||||||
      function openMenu() {
 | 
					      function openMenu() {
 | 
				
			||||||
        isMenuOpen.value = true
 | 
					        isMenuOpen.value = true
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      function closeMenu() {
 | 
					      function closeMenu() {
 | 
				
			||||||
        isMenuOpen.value = false
 | 
					        isMenuOpen.value = false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return { isMenuOpen, openMenu, closeMenu, t }
 | 
					      function updateLanguage(option: IDropdownOption) {
 | 
				
			||||||
 | 
					        locale.value = option.value.toString()
 | 
				
			||||||
 | 
					        store.commit('setLanguage', option.value)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        availableLanguages,
 | 
				
			||||||
 | 
					        isMenuOpen,
 | 
				
			||||||
 | 
					        language: computed(() => store.state.language),
 | 
				
			||||||
 | 
					        t,
 | 
				
			||||||
 | 
					        openMenu,
 | 
				
			||||||
 | 
					        closeMenu,
 | 
				
			||||||
 | 
					        updateLanguage,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@@ -74,7 +107,6 @@
 | 
				
			|||||||
      font-size: 1.2em;
 | 
					      font-size: 1.2em;
 | 
				
			||||||
      font-weight: bold;
 | 
					      font-weight: bold;
 | 
				
			||||||
      margin-right: 10px;
 | 
					      margin-right: 10px;
 | 
				
			||||||
      line-height: 12px;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fa {
 | 
					    .fa {
 | 
				
			||||||
@@ -84,6 +116,7 @@
 | 
				
			|||||||
    .nav-icon-open {
 | 
					    .nav-icon-open {
 | 
				
			||||||
      display: none;
 | 
					      display: none;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .hamburger-icon,
 | 
					    .hamburger-icon,
 | 
				
			||||||
    .close-icon {
 | 
					    .close-icon {
 | 
				
			||||||
      display: none;
 | 
					      display: none;
 | 
				
			||||||
@@ -99,8 +132,25 @@
 | 
				
			|||||||
        display: none;
 | 
					        display: none;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .nav-items-app-menu,
 | 
				
			||||||
 | 
					      .nav-items-user-menu {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        margin: 0;
 | 
				
			||||||
 | 
					        padding: 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      .nav-item {
 | 
					      .nav-item {
 | 
				
			||||||
        padding: 10px 10px;
 | 
					        padding: 0 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &.dropdown-wrapper {
 | 
				
			||||||
 | 
					          width: 60px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ::v-deep(.dropdown-list) {
 | 
				
			||||||
 | 
					          margin-left: -10px;
 | 
				
			||||||
 | 
					          padding-left: 10px;
 | 
				
			||||||
 | 
					          width: 75px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -155,9 +205,13 @@
 | 
				
			|||||||
          justify-content: space-between;
 | 
					          justify-content: space-between;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          .app-name {
 | 
					          .app-name {
 | 
				
			||||||
            padding: 19px 25px;
 | 
					            padding: 15px 25px;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .nav-item {
 | 
				
			||||||
 | 
					          padding: 7px 25px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,8 @@ function loadLocaleMessages(): LocaleMessages<VueMessageType> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default createI18n({
 | 
					export default createI18n({
 | 
				
			||||||
  legacy: false,
 | 
					  legacy: false,
 | 
				
			||||||
  locale: process.env.VUE_APP_I18N_LOCALE || 'en',
 | 
					  locale: 'en',
 | 
				
			||||||
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
 | 
					  fallbackLocale: 'en',
 | 
				
			||||||
 | 
					  globalInjection: true,
 | 
				
			||||||
  messages: loadLocaleMessages(),
 | 
					  messages: loadLocaleMessages(),
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
 | 
					  "LANGUAGE": "Language",
 | 
				
			||||||
  "LOGIN": "Login",
 | 
					  "LOGIN": "Login",
 | 
				
			||||||
  "LOGOUT": "Logout",
 | 
					  "LOGOUT": "Logout",
 | 
				
			||||||
  "REGISTER": "Register"
 | 
					  "REGISTER": "Register"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
 | 
					  "LANGUAGE": "Langue",
 | 
				
			||||||
  "LOGIN": "Se connecter",
 | 
					  "LOGIN": "Se connecter",
 | 
				
			||||||
  "LOGOUT": "Se déconnecter",
 | 
					  "LOGOUT": "Se déconnecter",
 | 
				
			||||||
  "REGISTER": "S'inscrire"
 | 
					  "REGISTER": "S'inscrire"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,14 @@
 | 
				
			|||||||
import { createStore } from 'vuex'
 | 
					import { createStore } from 'vuex'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default createStore({
 | 
					export default createStore({
 | 
				
			||||||
  state: {},
 | 
					  state: {
 | 
				
			||||||
  mutations: {},
 | 
					    language: 'en',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mutations: {
 | 
				
			||||||
 | 
					    setLanguage(state, language: string) {
 | 
				
			||||||
 | 
					      state.language = language
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  actions: {},
 | 
					  actions: {},
 | 
				
			||||||
  modules: {},
 | 
					  modules: {},
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								fittrackee_client/src/types/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								fittrackee_client/src/types/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export interface IDropdownOption {
 | 
				
			||||||
 | 
					  value: string | number
 | 
				
			||||||
 | 
					  label: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TDropdownOptions = IDropdownOption[]
 | 
				
			||||||
		Reference in New Issue
	
	Block a user