integrate login page wih api and keycloak

This commit is contained in:
Yusron alamsyah
2026-03-31 11:11:39 +07:00
parent acf491f8aa
commit d438fb0f5f
68 changed files with 1213 additions and 212 deletions
+9 -13
View File
@@ -4,6 +4,8 @@ import { useDisplay } from "vuetify";
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';
const sidebarMenu = shallowRef(sidebarItems);
const customizer = useCustomizerStore();
const { mdAndDown } = useDisplay();
@@ -46,19 +48,13 @@ watch(mdAndDown, (val) => {
<LazyLayoutFullVerticalHeader v-if="!customizer.setHorizontalLayout" />
</div>
<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-container fluid class="page-wrapper">
<div :class="customizer.boxed ? 'maxWidth' : ''">
<RouterView />
<AppSnackbar />
</div>
</v-container>
</v-main>
</v-app>
</v-locale-provider>
@@ -1,105 +1,66 @@
<!-- components/layout/ProfileDD.vue -->
<script setup lang="ts">
import { MailIcon } from "vue-tabler-icons";
import { profileDD } from "~/_mockApis/headerData";
import { PerfectScrollbar } from "vue3-perfect-scrollbar";
import { useUserInfo } from "~/composables/useUserInfo";
import { computed } from "vue";
import { useAuth } from "~/composables/useAuth";
import { computed, onMounted } from "vue";
const userInfo = useUserInfo();
const auth = useAuth();
onMounted(async () => {
if (!auth.isLoggedIn.value) {
await auth.fetchUserSession();
}
});
// Enhanced logout with proper error handling
const logout = async () => {
try {
// Use the updated logout method from useUserInfo that handles Keycloak logout and session clearing
await userInfo.logout({
reason: "idle",
confirmDialog: false // Show confirmation dialog
});
} catch (error) {
console.error("Logout from profile failed:", error);
}
await auth.logout();
};
// **TAMBAHAN: Full logout function dengan konfirmasi**
const fullLogout = async () => {
try {
// Tampilkan konfirmasi sebelum full logout
const confirmed = confirm(
"Apakah Anda yakin ingin keluar dari semua sesi? Ini akan menghapus semua data lokal dan sesi Keycloak."
);
const confirmed = confirm(
"Apakah Anda yakin ingin keluar dari semua sesi? Ini akan menghapus semua data lokal dan sesi Keycloak."
);
if (!confirmed) return;
if (!confirmed) return;
console.log("Initiating full logout from ProfileDD...");
// Gunakan fullLogout dari useUserInfo composable
await userInfo.fullLogout();
} catch (error) {
console.error("Full logout from profile failed:", error);
// Fallback jika fullLogout gagal
try {
console.log("Attempting fallback logout...");
await userInfo.logout({
reason: "manual",
clearStorage: true
});
} catch (fallbackError) {
console.error("Fallback logout also failed:", fallbackError);
// Last resort - force redirect
if (process.client) {
localStorage.clear();
sessionStorage.clear();
window.location.href = "/auth/login?reason=force";
}
}
}
await auth.fullLogout();
};
// **TAMBAHAN: Logout dengan konfirmasi untuk UX yang lebih baik**
const logoutWithConfirmation = async () => {
try {
const confirmed = confirm("Apakah Anda yakin ingin keluar?");
const confirmed = confirm("Apakah Anda yakin ingin keluar?");
if (!confirmed) return;
if (!confirmed) return;
await logout();
} catch (error) {
console.error("Logout with confirmation failed:", error);
}
await logout();
};
// Get user display info from session
const getUserDisplayInfo = () => {
if (!userInfo.user.value && !userInfo.data.value?.user)
if (!auth.user.value)
return {
name: "Guest User",
email: "guest@example.com",
role: "Guest"
};
const user = userInfo.user.value || userInfo.data.value?.user || {};
const user = auth.user.value || {};
return {
name: user.name || user.given_name || user.preferred_username || "User",
name: user.name || "User",
email: user.email || "No email",
role: userInfo.userRoles.value[0] || "User"
role: auth.userRoles.value[0] || user.role || "User",
provider :user.auth_provider
};
};
const displayInfo = computed(() => getUserDisplayInfo());
// Computed properties for decodedToken and clientScopes
// const decodedToken = computed(() => userInfo.decodedToken.value);
// const clientScopes = computed(() => userInfo.clientScopes.value);
// **TAMBAHAN: Computed property untuk menampilkan status session**
const sessionInfo = computed(() => {
return {
isAuthenticated: userInfo.isAuthenticated.value,
sessionExpires: userInfo.sessionExpires.value,
hasValidToken: !!userInfo.idToken.value
isAuthenticated: auth.isLoggedIn.value,
sessionExpires: auth.sessionData.value?.expiresAt || null,
hasValidToken: !!auth.sessionData.value?.accessToken
};
});
</script>
@@ -127,10 +88,10 @@ const sessionInfo = computed(() => {
</v-avatar>
<div class="ml-3">
<h6 class="text-h6 mb-n1">{{ displayInfo.name }}</h6>
<span class="text-subtitle-1 font-weight-regular textSecondary">
<!-- <span class="text-subtitle-1 font-weight-regular textSecondary">
{{ displayInfo.role }}
</span>
<div class="d-flex align-center mt-1">
</span> -->
<div class="d-flex align-center mt-2">
<MailIcon size="18" stroke-width="1.5" />
<span
class="text-subtitle-1 font-weight-regular textSecondary ml-2"
@@ -151,46 +112,10 @@ const sessionInfo = computed(() => {
</div>
</div>
</div>
<v-divider></v-divider>
</div>
<PerfectScrollbar style="height: calc(100vh - 240px); max-height: 240px">
<v-list class="py-0 theme-list" lines="two">
<v-list-item
v-for="item in profileDD"
:key="item.title"
class="py-4 px-8 custom-text-primary"
:to="item.href"
>
<template v-slot:prepend>
<v-avatar
size="48"
color="lightprimary"
class="mr-3"
rounded="md"
>
<img
:src="item.avatar"
width="24"
height="24"
:alt="item.avatar"
/>
</v-avatar>
</template>
<div>
<h6 class="text-subtitle-1 font-weight-bold mb-2 custom-title">
{{ item.title }}
</h6>
</div>
<p class="text-subtitle-1 font-weight-regular textSecondary">
{{ item.subtitle }}
</p>
</v-list-item>
</v-list>
</PerfectScrollbar>
<!-- **DIPERBAIKI: Logout buttons section dengan multiple options** -->
<div class="pt-4 pb-6 px-8">
<div class="pt-0 pb-6 px-8">
<!-- Regular Logout Button -->
<v-btn
color="primary"
@@ -205,6 +130,7 @@ const sessionInfo = computed(() => {
<!-- **TAMBAHAN: Full Logout Button** -->
<v-btn
v-if="displayInfo.provider === 'keycloak'"
color="error"
variant="outlined"
block
@@ -214,13 +140,6 @@ const sessionInfo = computed(() => {
>
Full Logout
</v-btn>
<!-- **TAMBAHAN: Quick info text** -->
<div class="text-center mt-2">
<span class="text-caption textSecondary">
Full logout akan menghapus semua sesi dan data lokal
</span>
</div>
</div>
</v-sheet>
</v-menu>
@@ -214,17 +214,17 @@ const sidebarItem: menu[] = [
{
title: 'Banners',
icon: 'gallery-wide-line-duotone',
to: '/widgets/banners'
to: '/template/widgets/banners'
},
{
title: 'Cards',
icon: 'layers-minimalistic-line-duotone',
to: '/widgets/cards'
to: '/template/widgets/cards'
},
{
title: 'Charts',
icon: 'chart-line-duotone',
to: '/widgets/charts'
to: '/template/widgets/charts'
},
]
},
@@ -236,67 +236,67 @@ const sidebarItem: menu[] = [
{
title: 'Alerts',
icon: 'danger-triangle-line-duotone',
to: '/ui-components/alerts'
to: '/template/ui-components/alerts'
},
{
title: 'Avatar',
icon: 'user-circle-line-duotone',
to: '/ui-components/avatar'
to: '/template/ui-components/avatar'
},
{
title: 'Buttons',
icon: 'ghost-line-duotone',
to: '/ui-components/buttons'
to: '/template/ui-components/buttons'
},
{
title: 'Cards',
icon: 'layers-minimalistic-line-duotone',
to: '/ui-components/cards'
to: '/template/ui-components/cards'
},
{
title: 'Chip',
icon: 'tag-horizontal-line-duotone',
to: '/ui-components/chip'
to: '/template/ui-components/chip'
},
{
title: 'Dialogs',
icon: 'window-frame-line-duotone',
to: '/ui-components/dialogs'
to: '/template/ui-components/dialogs'
},
{
title: 'Expansion Panel',
icon: 'hamburger-menu-line-duotone',
to: '/ui-components/expansionPanel'
to: '/template/ui-components/expansionPanel'
},
{
title: 'List',
icon: 'list-line-duotone',
to: '/ui-components/list'
to: '/template/ui-components/list'
},
{
title: 'Menus',
icon: 'menu-dots-line-duotone',
to: '/ui-components/menus'
to: '/template/ui-components/menus'
},
{
title: 'Ratting',
icon: 'star-line-duotone',
to: '/ui-components/ratting'
to: '/template/ui-components/ratting'
},
{
title: 'Tables',
icon: 'tablet-line-duotone',
to: '/ui-components/tables'
to: '/template/ui-components/tables'
},
{
title: 'Tabs',
icon: 'notebook-line-duotone',
to: '/ui-components/tabs'
to: '/template/ui-components/tabs'
},
{
title: 'Tooltip',
icon: 'chat-round-dots-line-duotone',
to: '/ui-components/tooltip'
to: '/template/ui-components/tooltip'
}
]
},
@@ -307,12 +307,12 @@ const sidebarItem: menu[] = [
{
title: 'Shadow',
icon: 'copy-line-duotone',
to: '/style-components/shadow'
to: '/template/style-components/shadow'
},
{
title: 'Typography',
icon: 'text-bold-circle-line-duotone',
to: '/style-components/typography'
to: '/template/style-components/typography'
}
]
},
@@ -324,37 +324,37 @@ const sidebarItem: menu[] = [
{
title: 'Overview',
icon: 'widget-5-line-duotone',
to: '/shared-components'
to: '/template/shared-components'
},
{
title: 'UiParentCard & UiChildCard',
icon: 'layers-minimalistic-line-duotone',
to: '/shared-components/UiParentCard'
to: '/template/shared-components/UiParentCard'
},
{
title: 'WidgetCard & WidgetCardv2',
icon: 'chart-square-line-duotone',
to: '/shared-components/WidgetCards'
to: '/template/shared-components/WidgetCards'
},
{
title: 'Card Components',
icon: 'card-2-line-duotone',
to: '/shared-components/CardComponents'
to: '/template/shared-components/CardComponents'
},
{
title: 'BaseBreadcrumb',
icon: 'route-line-duotone',
to: '/shared-components/BaseBreadcrumb'
to: '/template/shared-components/BaseBreadcrumb'
},
{
title: 'UiTextfieldPrimary',
icon: 'text-field-line-duotone',
to: '/shared-components/UiTextfieldPrimary'
to: '/template/shared-components/UiTextfieldPrimary'
},
{
title: 'AppBaseCard',
icon: 'sidebar-minimalistic-line-duotone',
to: '/shared-components/AppBaseCard'
to: '/template/shared-components/AppBaseCard'
},
]
},