feat : middleware and refresh token

This commit is contained in:
Yusron alamsyah
2026-04-06 13:55:31 +07:00
parent d438fb0f5f
commit 4325bae76f
16 changed files with 1127 additions and 476 deletions
+13 -8
View File
@@ -5,6 +5,8 @@ import sidebarItems from "~/components/layout/full/vertical-sidebar/sidebarItem"
import { Menu2Icon } from "vue-tabler-icons";
import { useCustomizerStore } from "~/store/customizer";
import AppSnackbar from '~/components/shared/AppSnackbar.vue';
import Customizer from "~/components/layout/full/customizer/Customizer.vue";
const sidebarMenu = shallowRef(sidebarItems);
const customizer = useCustomizerStore();
@@ -21,6 +23,7 @@ watch(mdAndDown, (val) => {
<template>
<!------Sidebar-------->
<v-locale-provider>
<AppSnackbar />
<v-app
:theme="customizer.actTheme"
class="bg-containerBg"
@@ -47,14 +50,16 @@ watch(mdAndDown, (val) => {
<div :class="customizer.boxed ? 'maxWidth' : 'full-header'">
<LazyLayoutFullVerticalHeader v-if="!customizer.setHorizontalLayout" />
</div>
<v-main>
<v-container fluid class="page-wrapper">
<div :class="customizer.boxed ? 'maxWidth' : ''">
<RouterView />
<AppSnackbar />
</div>
</v-container>
<v-main class="ml-md-4">
<div class="rtl-lyt mb-3 hr-layout bg-containerBg">
<v-container fluid class="page-wrapper bg-background pt-md-8 rounded-xl">
<div>
<div class="">
<NuxtPage />
</div>
</div>
</v-container>
</div>
</v-main>
</v-app>
</v-locale-provider>
@@ -2,7 +2,8 @@
<script setup lang="ts">
import { MailIcon } from "vue-tabler-icons";
import { useAuth } from "~/composables/useAuth";
import { computed, onMounted } from "vue";
import { computed, onMounted, ref } from "vue";
import DialogConfrim from "~/components/shared/DialogConfrim.vue";
const auth = useAuth();
@@ -12,29 +13,39 @@ onMounted(async () => {
}
});
// Enhanced logout with proper error handling
const logout = async () => {
await auth.logout();
const isLogoutDialogOpen = ref(false);
const isFullLogoutDialogOpen = ref(false);
const isLoggingOut = ref(false);
const isFullLoggingOut = ref(false);
const openLogoutDialog = () => {
isLogoutDialogOpen.value = true;
};
// **TAMBAHAN: Full logout function dengan konfirmasi**
const fullLogout = async () => {
const confirmed = confirm(
"Apakah Anda yakin ingin keluar dari semua sesi? Ini akan menghapus semua data lokal dan sesi Keycloak."
);
if (!confirmed) return;
await auth.fullLogout();
const openFullLogoutDialog = () => {
isFullLogoutDialogOpen.value = true;
};
// **TAMBAHAN: Logout dengan konfirmasi untuk UX yang lebih baik**
const logoutWithConfirmation = async () => {
const confirmed = confirm("Apakah Anda yakin ingin keluar?");
const confirmLogout = async () => {
if (isLoggingOut.value) return;
isLoggingOut.value = true;
try {
await auth.logout();
isLogoutDialogOpen.value = false;
} finally {
isLoggingOut.value = false;
}
};
if (!confirmed) return;
await logout();
const confirmFullLogout = async () => {
if (isFullLoggingOut.value) return;
isFullLoggingOut.value = true;
try {
await auth.fullLogout();
isFullLogoutDialogOpen.value = false;
} finally {
isFullLoggingOut.value = false;
}
};
// Get user display info from session
@@ -121,7 +132,7 @@ const sessionInfo = computed(() => {
color="primary"
variant="outlined"
block
@click="logoutWithConfirmation"
@click="openLogoutDialog"
prepend-icon="mdi-logout"
class="mb-2"
>
@@ -134,13 +145,35 @@ const sessionInfo = computed(() => {
color="error"
variant="outlined"
block
@click="fullLogout"
@click="openFullLogoutDialog"
prepend-icon="mdi-logout-variant"
class="mb-2"
>
Full Logout
</v-btn>
</div>
<DialogConfrim
v-model="isLogoutDialogOpen"
title="Logout"
message="Apakah Anda yakin ingin keluar?"
confirm-text="Ya"
cancel-text="Tidak"
:loading="isLoggingOut"
:close-on-confirm="false"
@confirm="confirmLogout"
/>
<DialogConfrim
v-model="isFullLogoutDialogOpen"
title="Full Logout"
message="Apakah Anda yakin ingin keluar dari semua sesi? Ini akan menghapus semua data lokal dan sesi Keycloak."
confirm-text="Ya"
cancel-text="Tidak"
:loading="isFullLoggingOut"
:close-on-confirm="false"
@confirm="confirmFullLogout"
/>
</v-sheet>
</v-menu>
</template>
@@ -44,14 +44,14 @@ watch(priority, (newPriority) => {
<!-- ---------------------------------------------- -->
<!-- Search part -->
<!-- ---------------------------------------------- -->
<Searchbar />
<!-- <Searchbar /> -->
<!-- ---------------------------------------------- -->
<!-- Mega menu -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<!-- <div class="hidden-sm-and-down">
<Navigations />
</div>
</div> -->
<v-spacer class="hidden-sm-and-down" />
@@ -63,14 +63,14 @@ watch(priority, (newPriority) => {
</div>
<ThemeToggler />
<!-- <ThemeToggler /> -->
<!-- ---------------------------------------------- -->
<!-- translate -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<!-- <div class="hidden-sm-and-down">
<LanguageDD />
</div>
</div> -->
<!-- ---------------------------------------------- -->
<!-- ShoppingCart -->
@@ -84,9 +84,9 @@ watch(priority, (newPriority) => {
<!-- ---------------------------------------------- -->
<!-- Notification -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<!-- <div class="hidden-sm-and-down">
<NotificationDD />
</div>
</div> -->
<!-- ---------------------------------------------- -->
<!-- User Profile -->
@@ -17,349 +17,27 @@ export interface menu {
}
const sidebarItem: menu[] = [
{
header: 'dashboards',
header: 'Menu',
id: 1,
children: [
{
title: 'Dashboard1',
title: 'Dashboard',
icon: 'widget-add-line-duotone',
to: '/dashboards/dashboard1'
to: '/apps/dashboard'
},
{
title: 'Dashboard2',
icon: 'chart-line-duotone',
to: '/dashboards/dashboard2'
},
{
title: 'Dashboard3',
icon: 'screencast-2-line-duotone',
to: '/dashboards/dashboard3'
},
{
title: 'Front Pages',
icon: 'home-angle-linear',
title: 'Settings',
icon: 'settings-outline',
to: '/',
children: [
{
title: 'Homepage',
to: '/front-page/homepage'
},
{
title: 'About Us',
to: '/front-page/about-us'
},
{
title: 'Blog',
to: '/front-page/blog/posts'
},
{
title: 'Blog Details',
to: '/front-page/blog/early-black-friday-amazon-deals-cheap-tvs-headphones'
},
{
title: 'Contact Us',
to: '/front-page/contact-us'
},
{
title: 'Portfolio',
to: '/front-page/portfolio'
},
{
title: 'Pricing',
to: '/front-page/pricing'
title: 'Menu',
to: '/apps/setting/pages',
}
]
},
]
},
{
header: 'apps',
id: 1,
children: [
{
title: 'ECommerce',
icon: 'cart-3-line-duotone',
to: '/ecommerce/',
children: [
{
title: 'Shop',
to: '/ecommerce/products'
},
{
title: 'Detail',
to: '/ecommerce/product/detail/1'
},
{
title: 'List',
to: '/ecommerce/productlist'
},
{
title: 'Checkout',
to: '/ecommerce/checkout'
},
{
title: 'Add Product',
to: '/ecommerce/add-product'
},
{
title: 'Edit Product',
to: '/ecommerce/edit-product'
}
]
},
{
title: 'Blog',
icon: 'widget-4-line-duotone',
to: '/',
children: [
{
title: 'Blog Posts',
to: '/apps/blog/posts'
},
{
title: 'Blog Details',
to: '/apps/blog/early-black-friday-amazon-deals-cheap-tvs-headphones'
}
]
},
{
title: 'User Profile',
icon: 'shield-user-line-duotone',
to: '/',
children: [
{
title: 'Profile',
to: '/apps/user/profile'
},
{
title: 'Followers',
to: '/apps/user/profile/followers'
},
{
title: 'Friends',
to: '/apps/user/profile/friends'
},
{
title: 'Gallery',
to: '/apps/user/profile/gallery'
}
]
},
{
title: 'Invoice',
icon: 'bill-check-outline',
to: '/',
children: [
{
title: 'List',
to: '/apps/invoice'
},
{
title: 'Details',
to: '/apps/invoice/details/102'
},
{
title: 'Create',
to: '/apps/invoice/create'
},
{
title: 'Edit',
to: '/apps/invoice/edit/102'
}
]
},
{
title: 'Calendar',
icon: 'calendar-mark-line-duotone',
to: '/apps/calendar'
},
{
title: 'Email',
icon: 'letter-linear',
to: '/apps/email'
},
{
title: 'Chats',
icon: 'chat-round-line-line-duotone',
to: '/apps/chats'
},
{
title: 'Notes',
icon: 'document-text-line-duotone',
to: '/apps/notes'
},
{
title: 'Kanban',
icon: 'airbuds-case-minimalistic-line-duotone',
to: '/apps/kanban'
},
{
title: 'Contact',
icon: 'iphone-line-duotone',
to: '/apps/contacts'
},
{
title: 'Tickets',
icon: 'ticker-star-outline',
to: '/apps/tickets'
},
]
},
{
header: 'Widgets',
id: 2,
children: [
{
title: 'Banners',
icon: 'gallery-wide-line-duotone',
to: '/template/widgets/banners'
},
{
title: 'Cards',
icon: 'layers-minimalistic-line-duotone',
to: '/template/widgets/cards'
},
{
title: 'Charts',
icon: 'chart-line-duotone',
to: '/template/widgets/charts'
},
]
},
{
header: 'UI Components',
id: 3,
children: [
{
title: 'Alerts',
icon: 'danger-triangle-line-duotone',
to: '/template/ui-components/alerts'
},
{
title: 'Avatar',
icon: 'user-circle-line-duotone',
to: '/template/ui-components/avatar'
},
{
title: 'Buttons',
icon: 'ghost-line-duotone',
to: '/template/ui-components/buttons'
},
{
title: 'Cards',
icon: 'layers-minimalistic-line-duotone',
to: '/template/ui-components/cards'
},
{
title: 'Chip',
icon: 'tag-horizontal-line-duotone',
to: '/template/ui-components/chip'
},
{
title: 'Dialogs',
icon: 'window-frame-line-duotone',
to: '/template/ui-components/dialogs'
},
{
title: 'Expansion Panel',
icon: 'hamburger-menu-line-duotone',
to: '/template/ui-components/expansionPanel'
},
{
title: 'List',
icon: 'list-line-duotone',
to: '/template/ui-components/list'
},
{
title: 'Menus',
icon: 'menu-dots-line-duotone',
to: '/template/ui-components/menus'
},
{
title: 'Ratting',
icon: 'star-line-duotone',
to: '/template/ui-components/ratting'
},
{
title: 'Tables',
icon: 'tablet-line-duotone',
to: '/template/ui-components/tables'
},
{
title: 'Tabs',
icon: 'notebook-line-duotone',
to: '/template/ui-components/tabs'
},
{
title: 'Tooltip',
icon: 'chat-round-dots-line-duotone',
to: '/template/ui-components/tooltip'
}
]
},
{
header: 'Style Components',
id: 3,
children: [
{
title: 'Shadow',
icon: 'copy-line-duotone',
to: '/template/style-components/shadow'
},
{
title: 'Typography',
icon: 'text-bold-circle-line-duotone',
to: '/template/style-components/typography'
}
]
},
{
header: 'Shared Components',
id: 3,
children: [
{
title: 'Overview',
icon: 'widget-5-line-duotone',
to: '/template/shared-components'
},
{
title: 'UiParentCard & UiChildCard',
icon: 'layers-minimalistic-line-duotone',
to: '/template/shared-components/UiParentCard'
},
{
title: 'WidgetCard & WidgetCardv2',
icon: 'chart-square-line-duotone',
to: '/template/shared-components/WidgetCards'
},
{
title: 'Card Components',
icon: 'card-2-line-duotone',
to: '/template/shared-components/CardComponents'
},
{
title: 'BaseBreadcrumb',
icon: 'route-line-duotone',
to: '/template/shared-components/BaseBreadcrumb'
},
{
title: 'UiTextfieldPrimary',
icon: 'text-field-line-duotone',
to: '/template/shared-components/UiTextfieldPrimary'
},
{
title: 'AppBaseCard',
icon: 'sidebar-minimalistic-line-duotone',
to: '/template/shared-components/AppBaseCard'
},
]
},
}
];
export default sidebarItem;