Skip to content

Commit

Permalink
✨ New account menu
Browse files Browse the repository at this point in the history
  • Loading branch information
moisout committed Jun 17, 2024
1 parent 1f1c95d commit baccfdf
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 31 deletions.
6 changes: 3 additions & 3 deletions client/app/components/header/CategoryHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
display: flex;
flex-direction: row;
gap: 10px;
opacity: 0.9;
opacity: 0.8;
.text {
color: var(--subtitle-color-light);
color: var(--theme-color);
font-size: 0.8rem;
margin: auto 0;
}
Expand All @@ -23,7 +23,7 @@
margin: auto 0;
width: 100%;
height: 1px;
background-color: var(--subtitle-color-light);
background-color: var(--theme-color);
}
}
</style>
59 changes: 56 additions & 3 deletions client/app/components/header/Menu.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import BadgeButton from '~/components/buttons/BadgeButton.vue';
import LoginForm from '../form/LoginForm.vue';
import RegisterForm from '../form/RegisterForm.vue';
import Settings from '~/components/Settings.vue';
Expand All @@ -18,6 +19,31 @@ const settingsOpen = ref(false);
const aboutOpen = ref(false);
const loginOpen = ref(false);
const registerOpen = ref(false);
const logoutOpen = ref(false);
const openPopup = (popup: string) => {
closeAllPopups();
popupStore.setPopupOpen(true);
switch (popup) {
case 'settings':
settingsOpen.value = true;
break;
case 'about':
aboutOpen.value = true;
break;
case 'login':
loginOpen.value = true;
break;
case 'register':
registerOpen.value = true;
break;
case 'logout':
logoutOpen.value = true;
break;
default:
break;
}
};
const onLoginClick = () => {
closeAllPopups();
Expand Down Expand Up @@ -71,6 +97,16 @@ const onEscape = (e: KeyboardEvent) => {
}
};
const logout = () => {
userStore.logout();
closeAllPopups();
logoutOpen.value = false;
};
const onLogoutPopupClose = () => {
closeAllPopups();
logoutOpen.value = false;
};
onMounted((): void => {
window.addEventListener('keydown', onEscape);
});
Expand Down Expand Up @@ -117,10 +153,11 @@ onBeforeUnmount((): void => {
>
<div class="user-icon">
<VTIcon v-if="!userStore.profileImage" name="mdi:account-circle" />

<div
v-if="userStore.profileImage"
class="user-image"
:style="{ 'background-image': `url(${getProfileImageUrl(userStore.profileImage)})` }"
:style="{ 'background-image': `url(${userStore.profileImage})` }"
/>
</div>
<p v-if="userAuthenticated" class="account-name">{{ userStore.username }}</p>
Expand All @@ -145,7 +182,22 @@ onBeforeUnmount((): void => {
<RegisterForm v-if="registerOpen" class="center-popup" :complete="closeAllPopups" />
</transition>
<transition name="fade-top-right">
<HeaderUserMenu v-if="accountMenuVisible" @close="closeAllPopups" />
<HeaderUserMenu
v-if="accountMenuVisible"
@close="closeAllPopups"
@open-popup="openPopup"
/>
</transition>
<transition name="fade-down">
<PopupConfirmation
v-if="logoutOpen"
:title="'Sign out'"
:message="'Do you want to sign out?'"
@close="onLogoutPopupClose"
>
<BadgeButton :click="onLogoutPopupClose">Cancel</BadgeButton>
<BadgeButton :click="logout">OK</BadgeButton>
</PopupConfirmation>
</transition>
</Teleport>
</ClientOnly>
Expand All @@ -167,7 +219,7 @@ onBeforeUnmount((): void => {
.fade-top-right-enter-from,
.fade-top-right-leave-to {
opacity: 0;
transform: translate3d(10px, -10px, 0) scale(0.9);
transform: translate3d(25px, -35px, 0) scale(0.9);
}
.fade-down-enter-active,
Expand Down Expand Up @@ -273,6 +325,7 @@ onBeforeUnmount((): void => {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
margin-right: 4px;
}
.vt-icon {
Expand Down
139 changes: 115 additions & 24 deletions client/app/components/header/UserMenu.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
<script setup lang="ts">
import { useUserStore } from '~/store/user';
const emit = defineEmits<{
(event: 'close'): void;
(event: 'openPopup', value: string): void;
}>();
const userStore = useUserStore();
const onNavClick = () => {
emit('close');
};
const openPopup = (popup: string) => {
emit('openPopup', popup);
};
</script>

<template>
<div class="user-menu">
<div class="user-account">
<ClientOnly>
<div
v-if="userStore.profileImage"
class="avatar-background"
:style="{ 'background-image': `url(${userStore.profileImage})` }"
/>
</ClientOnly>
<div class="user-avatar">
<img
v-if="userStore.profileImage"
Expand All @@ -25,81 +45,147 @@ const userStore = useUserStore();
</div>
</div>
<div class="page-navigation">
<nuxt-link v-if="!userStore.isLoggedIn" class="page-link" to="/login">
<nuxt-link
v-if="!userStore.isLoggedIn"
class="page-link mobile-only"
to="/login"
@click="onNavClick"
>
<VTIcon name="mdi:login-variant" />
<span>Sign in</span>
</nuxt-link>
<nuxt-link v-if="!userStore.isLoggedIn" class="page-link" to="/register">
<nuxt-link
v-if="!userStore.isLoggedIn"
class="page-link mobile-only"
to="/register"
@click="onNavClick"
>
<VTIcon name="mdi:register" />
<span>Sign up</span>
</nuxt-link>
<HeaderCategoryHeader v-if="userStore.isLoggedIn">You</HeaderCategoryHeader>
<nuxt-link v-if="userStore.isLoggedIn" class="page-link" to="/profile">
<nuxt-link v-if="userStore.isLoggedIn" class="page-link" to="/profile" @click="onNavClick">
<VTIcon name="mdi:account" />
<span>Profile</span>
</nuxt-link>
<nuxt-link v-if="userStore.isLoggedIn" class="page-link" to="/subscriptions">
<nuxt-link
v-if="userStore.isLoggedIn"
class="page-link"
to="/subscriptions"
@click="onNavClick"
>
<VTIcon name="mdi:youtube-subscription" />
<span>Subscriptions</span>
</nuxt-link>
<nuxt-link v-if="userStore.isLoggedIn" class="page-link" to="/history">
<nuxt-link v-if="userStore.isLoggedIn" class="page-link" to="/history" @click="onNavClick">
<VTIcon name="mdi:history" />
<span>History</span>
</nuxt-link>
<nuxt-link v-if="userStore.isLoggedIn" class="page-link" to="/profile">
<nuxt-link
v-if="userStore.isLoggedIn"
class="page-link"
to="/subscriptions/manage"
@click="onNavClick"
>
<VTIcon name="mdi:pencil-box-multiple-outline" />
<span>Manage subscriptions</span>
</nuxt-link>
<HeaderCategoryHeader>Manage</HeaderCategoryHeader>
<button class="page-link" to="/settings">
<button class="page-link" data-testid="settings-link" @click="openPopup('settings')">
<VTIcon name="mdi:cog" /><span>Settings</span>
</button>
<nuxt-link v-if="userStore.isLoggedIn && userStore.admin" class="page-link" to="/account">
<nuxt-link
v-if="userStore.isLoggedIn && userStore.admin"
class="page-link"
to="/admin"
@click="onNavClick"
>
<VTIcon name="mdi:administrator" />
<span>Admin panel</span>
</nuxt-link>
<button v-if="userStore.isLoggedIn" class="page-link" to="/logout">
<button v-if="userStore.isLoggedIn" class="page-link" @click="openPopup('logout')">
<VTIcon name="mdi:logout-variant" />Logout
</button>
<HeaderCategoryHeader>Other</HeaderCategoryHeader>
<button class="page-link" to="/settings">
<button class="page-link" @click="openPopup('about')">
<VTIcon name="mdi:information-outline" /><span>About</span>
</button>
<button class="page-link" to="/settings">
<a
href="https://github.com/sponsors/ViewTube"
rel="noreferrer noopener"
target="_blank"
class="page-link"
@click="onNavClick"
>
<VTIcon name="mdi:heart" /><span>Donate</span>
</button>
</a>
</div>
</div>
</template>

<style lang="scss" scoped>
.popup-enter-active,
.popup-leave-active {
transition:
opacity 300ms $intro-easing,
transform 300ms $intro-easing;
}
.popup-enter-to,
.popup-leave-from {
opacity: 1;
transform: scale(1);
}
.popup-enter-from,
.popup-leave-to {
opacity: 0;
transform: scale(1.1);
}
.user-menu {
position: fixed;
top: $header-height + 15px;
right: 20px;
width: calc(100% - 40px);
max-height: calc(100% - $header-height - 30px);
max-width: 350px;
z-index: 1000;
padding-bottom: 10px;
background-color: var(--bgcolor-alt);
border-radius: 12px;
border-radius: 16px;
box-sizing: border-box;
overflow: hidden;
overflow: hidden auto;
@media screen and (max-width: $mobile-width) {
max-width: 100%;
}
.user-account {
display: grid;
grid-template-columns: 50px 1fr;
grid-template-rows: 50px;
gap: 10px;
background-color: var(--bgcolor-alt);
padding: 15px;
border-radius: 12px 12px 0 0;
position: relative;
grid-template-columns: 60px 1fr;
grid-template-rows: 60px;
gap: 15px;
background-color: var(--bgcolor-alt-light);
padding: 10px;
margin: 10px;
border-radius: 10px;
transition: background-color 200ms $intro-easing;
overflow: hidden;
.avatar-background {
position: absolute;
width: 100%;
height: 100%;
bottom: 0;
z-index: 10;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
filter: blur(25px) brightness(0.4);
}
.user-avatar {
z-index: 11;
.user-icon {
width: 100%;
height: 100%;
Expand All @@ -116,6 +202,8 @@ const userStore = useUserStore();
.user-info {
display: flex;
flex-direction: column;
margin: auto 0;
z-index: 11;
.user-name {
font-size: 1.1rem;
Expand Down Expand Up @@ -151,13 +239,16 @@ const userStore = useUserStore();
background-color: unset;
color: var(--subtitle-color);
border: none;
line-height: 24px;
&:hover {
color: var(--theme-color);
&.mobile-only {
@media screen and (min-width: $mobile-width) {
display: none;
}
}
&:last-child {
border-radius: 0 0 12px 12px;
&:hover {
color: var(--theme-color);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/cypress/e2e/2-features/settings.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('Settings tests', () => {

it('Settings popup opens and closes properly', () => {
cy.get('#account').click();
cy.get('#settings-btn').should('exist').click();
cy.get('button[data-testid=settings-link]').should('exist').click();
cy.get('.settings.popup').should('exist');

cy.get('.settings.popup .settings-header .settings-title').text().should('eq', 'Settings');
Expand Down

0 comments on commit baccfdf

Please sign in to comment.