Files
template-nuxtsim/components/auth/LoginForm.vue
T

315 lines
8.7 KiB
Vue
Executable File

<!-- components/auth/LoginForm.vue -->
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from "vue";
import { useUserInfo } from "~/composables/useUserInfo";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const userInfo = useUserInfo();
// State
const isLoggingIn = ref(false);
const isLoggingOut = ref(false);
const errorMessage = ref("");
const showDebugInfo = ref(false);
// **PERBAIKAN: Enhanced Query Parameter Handling**
const reason = computed(() => route.query.reason as string);console.log("reason:", reason.value);
const shouldShowContinue = computed(() => route.query.continue === "true");
const returnUrl = computed(() => (route.query.returnUrl as string) || "/");console.log("returnUrl:", returnUrl.value);
const getReasonMessage = () => {
// console.log("LoginForm: current reason value:", reason.value);
switch (reason.value) {
case "idle":
return {
type: "warning" as const,
title: "Sesi Tidak Aktif",
message:
"Anda telah tidak aktif selama 15 menit. Sesi Keycloak masih valid.",
icon: "mdi-clock-alert-outline"
};
case "tab_inactive":
return {
type: "info" as const,
title: "Tab Kembali Aktif",
message:
"Tab telah tidak aktif lebih dari 10 menit. Sesi Keycloak masih valid.",
icon: "mdi-tab"
};
case "session_expired":
return {
type: "error" as const,
title: "Sesi Berakhir",
message: "Sesi Anda telah berakhir. Silakan login kembali.",
icon: "mdi-clock-alert"
};
case "session_error":
return {
type: "error" as const,
title: "Error Sesi",
message: "Terjadi error pada sesi Anda. Silakan login kembali.",
icon: "mdi-alert"
};
case "auth_required":
return {
type: "error" as const,
title: "Autentikasi Diperlukan",
message: "Anda harus login untuk mengakses halaman ini.",
icon: "mdi-lock-alert"
};
default:
return null;
}
};
// **PERBAIKAN: Enhanced Keycloak Login**
const signInKeycloak = async () => {
try {
isLoggingIn.value = true;
errorMessage.value = "";
await userInfo.login("keycloak");
} catch (error: any) {
errorMessage.value = error.message || "Login gagal. Silakan coba lagi.";
} finally {
isLoggingIn.value = false;
}
};
// **PERBAIKAN: Enhanced Continue Handler**
const handleContinue = async () => {
try {
// Update activity untuk reset idle timer
userInfo.updateActivity();
// Force refresh session to ensure session is valid
await userInfo.forceRefreshSession();
// Navigate ke return URL atau dashboard
await router.push(returnUrl.value);
} catch (error) {
console.error("Navigation error:", error);
errorMessage.value = "Gagal melanjutkan sesi. Silakan coba lagi.";
}
};
// **PERBAIKAN: Enhanced Logout Handler**
const handleSignOut = async () => {
try {
isLoggingOut.value = true;
await userInfo.fullLogout();
} catch (error) {
//console.error("Logout error:", error);
errorMessage.value = "Logout gagal. Silakan coba lagi.";
} finally {
isLoggingOut.value = false;
}
};
// **PERBAIKAN: Ubah tipe variabel untuk menerima null**
let cleanupSessionMonitoring: (() => void) | null = null;
// **PERBAIKAN: Enhanced onMounted dengan proper type handling**
onMounted(() => {
if (userInfo.isAuthenticated.value) {
const cleanup = userInfo.startSessionMonitoring();
if (cleanup) {
cleanupSessionMonitoring = cleanup;
}
}
});
// **PERBAIKAN: Enhanced onUnmounted dengan null check**
onUnmounted(() => {
if (cleanupSessionMonitoring) {
cleanupSessionMonitoring();
cleanupSessionMonitoring = null;
}
});
// User display functions
const getUserDisplayName = () => {
if (!userInfo.user.value) return "";
const user = userInfo.user.value as any;
return (
user.name ||
user.given_name ||
`${user.given_name || ""} ${user.family_name || ""}`.trim() ||
user.preferred_username ||
user.username ||
user.email?.split("@")[0] ||
"User"
);
};
console.log("user is authenticated:", userInfo.isAuthenticated.value, shouldShowContinue.value);
</script>
<template>
<v-card-title class="text-h4 text-center mb-6">
<v-icon
icon="mdi-shield-account"
size="48"
color="primary"
class="mb-2"
></v-icon>
<br />
Masuk ke Sistem
</v-card-title>
<!-- **PERBAIKAN: Loading State** -->
<div v-if="userInfo.isLoading.value" class="text-center py-8">
<v-progress-circular
indeterminate
color="primary"
size="64"
></v-progress-circular>
<p class="text-body-1 mt-4">Memeriksa autentikasi...</p>
</div>
<!-- **PERBAIKAN: Continue Panel untuk User yang Sudah Authenticated** -->
<div
v-else-if="!shouldShowContinue || !reason || reason === 'auth_required'"
class="text-center"
>
<v-card-text>
<!-- **PERBAIKAN: Error Alert** -->
<v-alert
v-if="errorMessage"
type="error"
variant="tonal"
class="mb-6"
closable
@click:close="errorMessage = ''"
>
{{ errorMessage }}
</v-alert>
<!-- **PERBAIKAN: Reason Alert** -->
<v-alert
v-if="getReasonMessage()"
:type="getReasonMessage()!.type"
variant="tonal"
class="mb-6"
:icon="getReasonMessage()!.icon"
>
<div class="text-h6 mb-2">{{ getReasonMessage()!.title }}</div>
<div class="text-body-2">{{ getReasonMessage()!.message }}</div>
</v-alert>
<!-- Welcome Message -->
<div class="text-center mb-6">
<h5 class="text-h5 mb-2">Selamat Datang di Sistem RSSA</h5>
<p class="text-body-2 text-medium-emphasis">
Silakan masuk untuk melanjutkan ke dashboard Anda
</p>
</div>
<!-- **PERBAIKAN: Keycloak Login Button** -->
<v-btn
color="primary"
size="large"
variant="flat"
block
class="mb-4"
@click="signInKeycloak"
:loading="isLoggingIn"
:disabled="isLoggingIn"
prepend-icon="mdi-key"
>
<span>{{
isLoggingIn ? "Menghubungkan..." : "Masuk dengan Keycloak"
}}</span>
</v-btn>
<!-- Additional Info -->
<div class="text-center mt-6">
<p class="text-body-2 text-medium-emphasis">
Belum memiliki akun?
<span class="text-primary font-weight-medium">
Hubungi Administrator
</span>
</p>
</div>
</v-card-text>
</div>
<!-- **PERBAIKAN: Login Form untuk User yang Belum Authenticated** -->
<div v-else>
<v-card-text class="pb-4">
<!-- **PERBAIKAN: Reason-based Alert** -->
<v-alert
v-if="getReasonMessage()"
:type="getReasonMessage()!.type"
variant="tonal"
class="mb-6 text-left"
:icon="getReasonMessage()!.icon"
>
<div class="text-h6 mb-2">{{ getReasonMessage()!.title }}</div>
<div class="text-body-2">{{ getReasonMessage()!.message }}</div>
</v-alert>
<!-- Welcome Message -->
<v-alert
v-else
type="success"
variant="tonal"
class="mb-6 text-left"
icon="mdi-check-circle"
>
<div class="text-h6 mb-2">
Selamat datang kembali, <strong>{{ getUserDisplayName() }}</strong
>!
</div>
<div class="text-body-2">Anda masih terhubung dengan Keycloak.</div>
</v-alert>
<!-- Session Info -->
<v-card variant="outlined" class="mb-4">
<v-card-title class="text-h6 d-flex align-center">
<v-icon icon="mdi-account-circle" class="mr-2"></v-icon>
Informasi Sesi
</v-card-title>
<v-card-text>
<div class="d-flex justify-center">
<v-chip color="success" variant="outlined" size="small">
<v-icon start icon="mdi-clock-check"></v-icon>
Sesi Keycloak Aktif
</v-chip>
</div>
</v-card-text>
</v-card>
</v-card-text>
<!-- **PERBAIKAN: Action Buttons** -->
<v-card-actions class="justify-center px-6 pb-6">
<v-btn
color="primary"
size="large"
variant="flat"
class="mr-3 px-8"
@click="handleContinue"
prepend-icon="mdi-arrow-right"
>
Lanjutkan ke Aplikasi
</v-btn>
<v-btn
color="error"
size="large"
variant="outlined"
@click="handleSignOut"
prepend-icon="mdi-logout"
:loading="isLoggingOut"
:disabled="isLoggingOut"
>
{{ isLoggingOut ? "Keluar..." : "Keluar dari Keycloak" }}
</v-btn>
</v-card-actions>
</div>
</template>