conditional render of navbar based on whether user is signed in on AWS amplify - authentication

I am attempting to conditionally render a and based on whether or not a user is signed in or not using AWS amplify and Vue 3 for the frontend. I have been able to get it to not render and then render on sign in but when I log back out the navbar elements are still there and should have disappeared. I am new to Vue, so this maybe an easy fix but am unsure. I have tried making using both computed and a watch to try and force update the computed but that is not working. Any help would be much appreciated. The code is below:
<template>
<header>
<nav class="navbar">
<router-link #click="closeMenu" to="/" class="nav-branding"
>Portal</router-link
>
<ul :class="[menuIsOpen ? 'active' : '', 'nav-menu']" v-show="isSignedIn">
<li #click="toggleMenu" class="nav-item">
<router-link to="/pensions" class="nav-link">Pensions</router-link>
</li>
<li #click="toggleMenu" class="nav-item">
<router-link to="/benefits" class="nav-link">Benefits</router-link>
</li>
<li #click="toggleMenu" class="nav-item">
<router-link to="/annual-leave" class="nav-link"
>Annual Leave</router-link
>
</li>
<li #click="signOut" class="nav-item">
<router-link to="/" class="nav-link">Sign Out</router-link>
</li>
</ul>
<div
#click="toggleMenu"
:class="[menuIsOpen ? 'active' : '', 'hamburger']"
v-show="isSignedIn"
>
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</div>
</nav>
</header>
<div :class="[menuIsOpen ? 'pushed' : 'static']"></div>
</template>
<script>
import { Auth } from "aws-amplify";
export default {
name: "NavBar",
data() {
return {
menuIsOpen: false,
};
},
methods: {
toggleMenu() {
this.menuIsOpen = !this.menuIsOpen;
},
closeMenu() {
this.menuIsOpen = false;
},
async signOut() {
try {
await Auth.signOut();
// navigate to the login page or another route
this.$router.push("/");
} catch (err) {
console.log(err);
}
},
async isUser() {
try {
await Auth.currentAuthenticatedUser();
return true;
} catch {
return false;
}
},
},
computed: {
isSignedIn() {
return this.isUser();
},
watch: {
isSignedIn() {
this.$forceUpdate();
},
},
},
};
</script>
<style>
header {
height: auto;
background-color: #0d1520;
}
li {
list-style: none;
}
a {
color: white;
text-decoration: none;
}
.navbar {
min-height: 70px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
}
.nav-menu {
display: flex;
justify-content: space-around;
align-items: center;
gap: 60px;
}
.nav-branding {
font-size: 2rem;
color: #03e9f4;
}
.nav-branding:hover {
text-shadow: 0 0 5px #03e9f4, 0 0 25px #03e9f4, 0 0 50px #03e9f4,
0 0 100px #03e9f4;
}
.nav-link {
transition: 0.7s ease;
color: #03e9f4;
}
.nav-link:hover {
text-shadow: 0 0 5px #03e9f4, 0 0 25px #03e9f4, 0 0 50px #03e9f4,
0 0 100px #03e9f4;
}
.hamburger {
display: none;
cursor: pointer;
}
.bar {
display: block;
width: 25px;
height: 3px;
margin: 5px auto;
transition: all 0.3s ease-in-out;
background-color: #03e9f4;
}
.hamburger:hover .bar {
box-shadow: 0 0 5px #03e9f4, 0 0 25px #03e9f4, 0 0 50px #03e9f4,
0 0 100px #03e9f4;
}
#media (max-width: 768px) {
.static {
transition: all 0.5s ease-in-out;
padding-top: 0;
z-index: 0;
background-color: #151f31;
}
.pushed {
padding-top: 168px;
transition: padding 0.3s ease-in-out;
transition-delay: 0.2;
z-index: 0;
background-color: #151f31;
}
.hamburger {
display: block;
}
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
.hamburger.active .bar:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.active .bar:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
.nav-menu {
position: fixed;
left: 100%;
top: 70px;
gap: 0;
flex-direction: column;
background-color: #0d1520;
width: 100%;
text-align: center;
transition: 0.5s;
}
.nav-item {
margin: 16px 0;
}
.nav-menu.active {
z-index: 1;
left: 0;
}
}
</style>

Update
There're way too many "unknowns" in your problem thus it's difficult to give you a working answer. I'll give you hints but please read the Vue docs carefully to understand how to implement them and to also better understand the framework.
As you mentioned in the comments below, you're handling your sign in function in a "sibling" component and that both components are imported in the App.vue.
App.vue
views/
... NavBar.vue
... Login.vue
Option #1
Without the use of a state management, what you can do is move this code:
export default {
data() {
return { isSignedIn: false };
},
methods: {
async isAuthenticated() {
try {
await Auth.currentAuthenticatedUser();
this.isSignedIn = true;
} catch {
this.isSignedIn = false;
}
},
}
async mounted() {
await this.isAuthenticated();
}
}
in your App.vue, then use an emitter in the login component so that your App.vue can "listen" whenever the user has logged in successfully. You can use this.isAuthenticated() as the callback function in the emit event prop. Then, pass the this.isSignedIn state as a prop in your navbar component:
Login.vue
export default {
...
methods: {
signInUser() {
/** your sign in logic */
this.$emit('signIn')
}
}
...
}
App.vue
<!-- Sample template -->
<NavBar :show="isSignedIn" />
<Login #sign-in="isAuthenticated" />
Navbar.vue
export default {
props: ['show'] // you can then pass show in your v-show
}
Option #2
You can also conditionally render the entire navbar component. However, you need to re-structure your templates a bit:
App.vue
<Navbar v-show="isSignedIn"/>
Option #3
Ideally, App should not be responsible for managing the isSignedIn as the only components that use this state are the NavBar and Login components. With that said, consider using Pinia to manage the states between components.

Related

How to get route name in vueJS

I am trying to create a script in vueJS, to enable the navbar only when the pages are not login/register.
For this I am trying to use this.$route.name but I only get undefined in the console when I am console logging it. I have created a method to check for the route name and i am calling it when the components are mounted i.e using mounted fucntion.
app.vue code:
<template>
<div id="app">
<NavBar v-if="flag" />
<main>
<router-view/>
</main>
</div>
</template>
<script>
import NavBar from './components/NavBar.vue';
export default {
components: { NavBar },
data(){
return{
flag1 : false,
flag2 : false,
flag: false
}
},
mounted() {
let id = localStorage.id;
this.getrouteflags();
return {
id
}
},
methods : {
getrouteflags: function(){
console.log(this.$route.query.name)
if(this.$route.name == 'register'){
this.flag1 = true;
}
if(this.$route.name == 'login'){
this.flag2 = true;
}
this.flag = this.flag1 || this.flag2;
console.log(this.flag1,this.flag2)
}
}
}
</script>
<style>
nav{
}
#app{
width: 100%;
height: 100%;
}
html,
body {
height: 100%;
min-height: 75rem;
}
body {
justify-content: center;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
background-image: '../../backend/static/plot.png';
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
body,html{
padding: 0;
margin: 0;
min-height: 100vh;
width: 100%;
}
html{
background-color: white;
}
</style>
This is what I am getting in the console.
Can someone help me in resolving this issue?
I'm guessing you're using Vue2.
Have you tried adding a computed property:
computed: {
currentRouteName() {
return this.$route.name;
}
}
In case you're using Vue3 with a script setup you could use VueRouter's composable.
<script setup>
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
// Do stuff.
</script>
If you need more help, read VueRouter's guide.
v3 reference
v4 reference

How can i change style of the body when my modal it will be open?

How can i change the body{overflow:hidden} when my modal it will be open?
for example it will be my modal, when its open, i would like to apply this style body{overflow:hidden}
<div v-if="dialogFoundation">
i am using vuejs3, i am using setup(){...}
The best performance would be to use javascript plain. You can add Eventlistener top the modal trigger Element. In my example i use a button. If it triggered then you can use classList and assign the body a class. In my example .dark.
Vue version
<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
<div id="app">
<h1>{{message}}</h1>
<p></p>
<button #click="doSomething">Modal</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Welcome to Vue!'
};
},
methods: {
doSomething() {
const b = document.querySelector('body');
b.classList.toggle('dark');
}
}
};
</script>
<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
a,
button {
color: #4fc08d;
}
button {
background: none;
border: solid 1px;
border-radius: 2em;
font: inherit;
padding: 0.75em 2em;
}
.dark {
background: black;
opacity: 0.4;
}
</style>
Vanilla JS
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
const b = document.querySelector('body');
b.classList.toggle('dark');
})
.dark {
background: black;
opacity: 0.4;
}
<body>
<div></div>
<button>click</button>
</body>
You can use watchers in Vue.js for solving this problem.
When variables changes you can check whether it is true or not, and if true change overflow of body to hidden.
{
watch: {
dialogFoundation(dialogFoundation) {
document.body.style.overflow = dialogFoundation ? "hidden" : "auto"
}
}
}
But I think this is not good solution. You can set this styles to your app element
#app {
width: 100%;
height: 100%;
overflow: auto;
}
and you can change style of app element using Vue directives.
<template>
<div id="app" :class="{ hidden: dialogFoundation }">
Long text....
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const dialogFoundation = ref(true);
return { dialogFoundation };
},
};
</script>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
overflow: auto;
}
#app.hidden {
overflow: hidden;
}
</style>
Code in codesandbox - https://codesandbox.io/s/immutable-glitter-rwc2iy?file=/src/App.vue

VueJS transition-group is not working on images, how can I fade images?

I'm trying to create an image slider, which fades the images. Image 1 should fade-out at the same moment as image 2 fades in. In other words: there shouldn't be a gap between them. Right now it does nothing like fading. The code is working, as so far that when the user clicks "next", the current images disappears, 0.9s later the next image appears. There is a delay of 0.9s between them (the same amount as declared in the CSS), so somehow it recognizes the transition time. It is only not fading, after clicking the button it immediately disappears. What am I missing?
My code
<template>
<div>
<transition-group name='fade' tag='div'>
<div v-for="i in [currentIndex]" :key='i'>
<img :src="currentImg" />
</div>
</transition-group>
<a class="prev" #click="prev" href='#'>❮</a>
<a class="next" #click="next" href='#'>❯</a>
</div>
</template>
<script>
export default {
data() {
return {
images: [
'https://cdn.pixabay.com/photo/2015/12/12/15/24/amsterdam-1089646_1280.jpg',
'https://cdn.pixabay.com/photo/2016/02/17/23/03/usa-1206240_1280.jpg',
'https://cdn.pixabay.com/photo/2015/05/15/14/27/eiffel-tower-768501_1280.jpg',
'https://cdn.pixabay.com/photo/2016/12/04/19/30/berlin-cathedral-1882397_1280.jpg'
],
timer: null,
currentIndex: 0,
show: true
};
},
mounted: function () {
this.startSlide();
},
methods: {
startSlide: function () {
this.timer = setInterval(this.next, 4000);
},
next: function () {
this.currentIndex += 1;
},
prev: function () {
this.currentIndex -= 1;
},
},
computed: {
currentImg: function () {
return this.images[Math.abs(this.currentIndex) % this.images.length];
},
},
};
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: all 0.9s ease;
overflow: hidden;
visibility: visible;
position: absolute;
width:100%;
opacity: 1;
}
.fade-enter,
.fade-leave-to {
visibility: hidden;
width:100%;
opacity: 0;
}
img {
height:600px;
width:100%
}
.prev, .next {
cursor: pointer;
position: absolute;
top: 40%;
width: auto;
padding: 16px;
color: white;
font-weight: bold;
font-size: 18px;
transition: 0.7s ease;
border-radius: 0 4px 4px 0;
text-decoration: none;
user-select: none;
}
.next {
right: 0;
}
.prev {
left: 0;
}
.prev:hover, .next:hover {
background-color: rgba(0,0,0,0.9);
}
</style>
Change .fade-enter to .fade-enter-from.
See example
The position:absolute was messing things up here... Removed this line, now it works like a charm!

How to display a component when page is loading in nuxt

I am quite new to nuxt, and I need help here.
async asyncData({ params, route }) {
const { data } = await axios.get(
`${process.env.baseUrl}/homes/?search=${
params.search
}&home_status=${1}`
)
return {
homes: data.results,
}
}
I am trying to populate my component with data(using asyncData), but I want my skeleton loader to show if my page is loading. How do I do that in nuxt?
Here is the code for my skeleton loader;
<template>
<div class="placeholder-container">
<div class="placeholder wave">
<div class="square"></div>
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
</div>
</div>
</template>
<style scoped>
.placeholder-container {
width: 35rem;
margin: 15px auto 15px auto;
}
.placeholder {
padding: 10px;
width: 100%;
// border: 1px solid lightgrey;
display: flex;
flex-direction: column;
}
.placeholder div {
background: #e8e8e8;
}
.placeholder .square {
width: 100%;
height: 22rem;
border-radius: 1rem;
margin: 0 0 10px;
}
.placeholder .line {
height: 12px;
margin: 0 0 10px 0;
}
.placeholder .line:nth-child(2) {
width: 120px;
}
.placeholder .line:nth-child(3) {
width: 180px;
}
.placeholder .line:nth-child(4) {
width: 150px;
}
.placeholder.wave div {
animation: wave 1s infinite linear forwards;
-webkit-animation: wave 1s infinite linear forwards;
background: #f6f7f8;
background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%);
background-size: 800px 104px;
}
#keyframes wave {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
#-webkit-keyframes wave {
0% {
background-position: -468px 0;
}
100% {
background-position: 468px 0;
}
}
</style>
What I normally do without using nuxt, is to create a data variable(loading=true), and change it to false after I finish making the api call, but since asyncData runs in the server, how do I make that work? I will also appreciate it if there is a better way of doing something like this
Placeholder
To display a placeholder component on a particular page
during loading, switch from asyncData to the fetch hook, which exposes the $fetchState.pending flag that is set to true when complete:
<template>
<div>
<MyLoading v-if="$fetchState.pending" />
<MyContent v-else :posts="posts" />
</div>
</template>
<script>
export default {
data() {
return {
posts: []
}
},
async fetch() {
const { data } = await this.$axios.get(...)
this.posts = data
}
}
</script>
Customizing loading progress bar
Nuxt provides a default loading progress bar that appears at the top of the app while a page is loading. You could customize the progress bar's appearance:
// nuxt.config.js
export default {
loading: {
color: 'blue',
height: '5px'
}
}
Or you could specify your own custom loading component instead:
// nuxt.config.js
export default {
loading: '~/components/MyLoading.vue'
}
demo

vue-pagination-2 not working

I am brand new to VueJS and almost everything is working, except for pagination. As a matter of fact, I have zero warnings. The only thing appearing in the console is "[HMR] Waiting for update signal from WDS... log.js?4244:23" followed by a ">" on the next line.
With that said, the pagination is showing the correct number of pages - given the data coming from the JSON, but I do not know how to connect the pagination to the UL or the app.
At least when there is an error, I can figure something out. Any help is appreciated and thanks in advance.
<template>
<div class="container" id="app">
<span>VueJS-Example</span>
<ul class="list-group list-inline mHeaders">
<li class="list-group-item">Title</li>
<li class="list-group-item">Band</li>
<li class="list-group-item">Date Posted</li>
<li class="list-group-item">Downloads</li>
<li class="list-group-item">YouTube</li>
<li class="list-group-item">MP3</li>
</ul>
<ul :key="item.id" class="list-group list-inline" v-for="item in items">
<li class="list-group-item">
{{item.title}}
</li>
<li class="list-group-item">
{{item.original_band}}
</li>
<li class="list-group-item">
{{item.date_posted}}
</li>
<li class="list-group-item mZip">
<a v-bind:href="''+item.download_midi_tabs+''" target="_blank"></a>
</li>
<li class="list-group-item mYt">
<a v-bind:href="''+item.youtube_link+''" target="_blank"></a>
</li>
<li class="list-group-item mAudio">
<a v-bind:href="''+item.download_guitar_m4v+''" target="_blank"></a>
</li>
</ul>
<pagination :records="288" :per-page="30" #paginate="getPostsViaREST"></pagination>
</div>
</template>
<script>
import axios from 'axios'
import {Pagination} from 'vue-pagination-2'
export default {
name: 'App',
data: function () {
return {
items: [{
title: '',
original_band: '',
date_posted: '',
download_midi_tabs: '',
youtube_link: '',
download_guitar_m4v: ''
}]
}
},
created: function () {
this.getPostsViaREST()
},
methods: {
getPostsViaREST: function () {
axios.get('http://local.sites/getSongs.php')
.then(response => { this.items = response.data })
}
},
components: {
Pagination
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
a {
color: #999;
}
.current {
color: red;
}
ul {
padding: 0;
list-style-type: none;
}
li {
display: inline;
margin: 5px 5px;
}
ul.list-group:after {
clear: both;
display: block;
content: "";
}
.list-group-item {
float: left;
}
.list-group li{
max-width: 30%;
min-width: 50px;
min-height: 48px;
max-height: 48px;
}
.list-group li:first-child{
width: 200px;
cursor: pointer;
}
.list-group li:nth-child(2){
width: 200px;
}
.list-group li:nth-child(3){
width: 110px;
}
.list-group li:nth-child(4){
width: 48px;
}
.list-group li:nth-child(5){
width: 48px;
}
.list-group li:last-child{
width: 48px;
}
.mZip{
background: url("http://www.kronusproductions.com/songs_angular/assets/images/mZip.png");
display: block !important;
max-width: 48px;
height: 48px;
cursor: pointer;
}
.mYt{
background: url("http://www.kronusproductions.com/songs_angular/assets/images/youtube-icon_48x48.png");
display: block !important;
width: 48px;
height: 48px;
cursor: pointer;
}
.mAudio{
background: url("http://www.kronusproductions.com/songs_angular/assets/images/volume.png");
display: block !important;
width: 48px;
height: 48px;
cursor: pointer;
}
.mZip a{
display: block !important;
width: 48px;
height: 48px;
}
.mYt a{
display: block !important;
width: 48px;
height: 48px;
}
.mAudio a{
display: block !important;
width: 48px;
height: 48px;
}
.mHeaders li{
background-color: cornflowerblue;
font-size: 0.85rem;
color: white;
}
OK - this is somewhat of a hack, but it works.
The following URL is a working example:
Needed help from some old fashion native JavaScript on the index.html file. First, I needed to be able to read a hidden field that contained the number of entries coming from the JSON., followed by setting all the ULs to display none - the setTimeout is to make sure that the JSON file was loaded
<script type="text/javascript">
var mTO = setTimeout(function () {
for (var x = 31; x <= $(".numRows").val() - 1; x++) {
if (typeof (document.getElementById('sp_' + x)) === 'undefined') {
} else {
document.getElementById('sp_' + x).style.display = 'none'
}
}
}, 1000);
mTO;
</script>
This is followed by calling a computed user created method to set this.mVar to the number of elements/entries/properties - whatever name you want to use for it - for Vue to know how many elements, so that we could divide by the number of pages to paginate
computed: {
mFunc: function () {
this.mVar = Object.keys(this.items).length
return Object.keys(this.items).length
}
}
This is another portion of the aforementioned hack - depending on page clicked in the pagination section, this determines what to hide and what to show
setPage: function (page) {
this.page = page
// console.log(page)
for (var y = 0; y <= this.mVar - 1; y++) {
if (typeof (document.getElementById('sp_' + y)) === 'undefined') {
} else {
document.getElementById('sp_' + y).style.display = 'none'
}
}
for (var x = (30 * (this.page - 1)); x <= 30 * (this.page); x++) {
if (typeof (document.getElementById('sp_' + x)) === 'undefined' || (document.getElementById('sp_' + x)) == null) {
break
} else {
document.getElementById('sp_' + x).style.display = 'block'
}
}
}
In case you were wondering what the "30" is for, that is the number of ULs that I wanted to show per page.
The last portion of the hack is within the template section
<pagination :records="mFunc" :per-page="30" #paginate="setPage"></pagination>
<span style="display: none;"><input type="hidden" class="numRows" :value="this.mVar" /></span>
If you would like to use the entire, then you can find it on my github:
Github repo for vuejs-example