port-nimara-client-portal/pages/login.vue

176 lines
5.8 KiB
Vue

<template>
<v-app full-height>
<v-main class="container">
<v-container class="fill-height" fluid>
<v-row align="center" justify="center" class="fill-height">
<v-col cols="12" class="d-flex flex-column align-center">
<v-card class="pa-6" rounded max-width="450" elevation="2">
<v-row no-gutters>
<v-col cols="12">
<v-img src="/Port_Nimara_Logo_2_Colour_New_Transparent.png" width="200" class="mb-3 mx-auto" />
</v-col>
<!-- Keycloak SSO Login -->
<v-col cols="12" class="mb-4">
<v-btn
color="primary"
size="large"
block
@click="loginWithKeycloak"
prepend-icon="mdi-shield-account"
:loading="keycloakLoading"
>
Login with Single Sign-On
</v-btn>
</v-col>
<!-- Divider -->
<v-col cols="12">
<v-divider class="my-4">
<span class="text-caption">OR</span>
</v-divider>
</v-col>
<!-- Directus Login Form -->
<v-col cols="12">
<v-form @submit.prevent="submit" v-model="valid">
<v-row no-gutters>
<v-scroll-y-transition>
<v-col v-if="errorThrown" cols="12" class="my-3">
<v-alert
text="Invalid email address or password"
color="error"
variant="tonal"
/>
</v-col>
</v-scroll-y-transition>
<v-col cols="12">
<v-row dense>
<v-col cols="12" class="mt-4">
<v-text-field
v-model="emailAddress"
placeholder="Email address"
:disabled="loading"
:rules="[
(value) => !!value || 'Must not be empty',
(value) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ||
'Invalid email address',
]"
variant="outlined"
type="email"
autofocus
/>
</v-col>
<v-col cols="12">
<v-text-field
@click:append-inner="passwordVisible = !passwordVisible"
v-model="password"
placeholder="Password"
:disabled="loading"
:type="passwordVisible ? 'text' : 'password'"
:append-inner-icon="
passwordVisible ? 'mdi-eye' : 'mdi-eye-off'
"
:rules="[(value) => !!value || 'Must not be empty']"
autocomplete="current-password"
variant="outlined"
/>
</v-col>
<v-col cols="12">
<v-btn
text="Log in"
:disabled="!valid"
:loading="loading"
type="submit"
variant="tonal"
color="primary"
size="large"
block
/>
</v-col>
</v-row>
</v-col>
</v-row>
</v-form>
</v-col>
</v-row>
</v-card>
<!-- PWA Install Banner -->
<PWAInstallBanner />
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</template>
<script lang="ts" setup>
// Define page meta for public access
definePageMeta({
auth: false
});
// Directus auth
const { login: directusLogin } = useDirectusAuth();
// Custom Keycloak auth
const { login: keycloakLogin } = useCustomAuth();
const loading = ref(false);
const keycloakLoading = ref(false);
const errorThrown = ref(false);
const emailAddress = ref();
const password = ref();
const passwordVisible = ref(false);
const valid = ref(false);
// SSO login function using custom Keycloak auth
const loginWithKeycloak = async () => {
try {
keycloakLoading.value = true;
console.log('[LOGIN] Starting SSO authentication via custom Keycloak auth...');
// Use our custom Keycloak authentication
keycloakLogin();
} catch (error) {
console.error('[LOGIN] SSO login error:', error);
const toast = useToast();
toast.error('SSO login failed. Please try again or use email/password login.');
} finally {
keycloakLoading.value = false;
}
};
// Directus login function
const submit = async () => {
try {
loading.value = true;
await directusLogin({ email: emailAddress.value, password: password.value });
return navigateTo("/dashboard");
} catch (error) {
errorThrown.value = true;
} finally {
loading.value = false;
}
};
useHead({
title: "Login",
});
</script>
<style>
.container {
background: url(/background.jpg);
background-repeat: no-repeat;
background-size: cover;
}
</style>