Implement Password Grant Flow for Authentication and Enhance User Experience
- Introduced Password Grant Flow for user authentication, allowing direct login with email and password. - Updated `useAuth` composable to manage login and logout processes, including Single Sign-Out from Cidaas. - Enhanced user interface with a new `UserMenu` component displaying user information and logout functionality. - Updated homepage to show personalized greetings for logged-in users and a login prompt for guests. - Added logout confirmation page with a countdown redirect to the homepage. - Documented the implementation details and future enhancements for OAuth2 flows in CLAUDE.md and other relevant documentation. - Added test credentials and guidelines for automated testing in the new TESTING.md file.
This commit is contained in:
90
app/components/UserMenu.vue
Normal file
90
app/components/UserMenu.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { User, LogOut } from 'lucide-vue-next'
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from './ui/avatar'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from './ui/dropdown-menu'
|
||||
|
||||
const { user, logout } = useAuth()
|
||||
|
||||
/**
|
||||
* Get user initials for Avatar fallback
|
||||
* Example: "Bastian Masanek" → "BM"
|
||||
*/
|
||||
const userInitials = computed(() => {
|
||||
if (!user.value) return '?'
|
||||
|
||||
const first = user.value.firstName?.charAt(0)?.toUpperCase() || ''
|
||||
const last = user.value.lastName?.charAt(0)?.toUpperCase() || ''
|
||||
|
||||
return first + last || user.value.email?.charAt(0)?.toUpperCase() || '?'
|
||||
})
|
||||
|
||||
/**
|
||||
* Handle logout click
|
||||
*/
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await logout()
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button
|
||||
class="flex items-center gap-2 rounded-full focus:outline-none focus:ring-2 focus:ring-experimenta-accent focus:ring-offset-2 focus:ring-offset-experimenta-purple transition-all hover:scale-105 hover:shadow-lg"
|
||||
aria-label="Benutzermenü öffnen"
|
||||
>
|
||||
<Avatar class="h-12 w-12 border-3 border-experimenta-accent shadow-md bg-experimenta-accent">
|
||||
<AvatarImage :src="undefined" :alt="user?.firstName" />
|
||||
<AvatarFallback class="bg-experimenta-accent text-white font-bold text-base flex items-center justify-center w-full h-full">
|
||||
{{ userInitials }}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent align="end" class="w-56 z-[200]">
|
||||
<!-- User email (non-interactive) -->
|
||||
<DropdownMenuLabel class="font-normal">
|
||||
<div class="flex flex-col space-y-1">
|
||||
<p class="text-sm font-medium leading-none">
|
||||
{{ user?.firstName }} {{ user?.lastName }}
|
||||
</p>
|
||||
<p class="text-xs leading-none text-muted-foreground">
|
||||
{{ user?.email }}
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<!-- Profile (disabled for now, placeholder for Phase 2+) -->
|
||||
<DropdownMenuItem disabled>
|
||||
<User class="mr-2 h-4 w-4" />
|
||||
<span>Profil</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<!-- Logout -->
|
||||
<DropdownMenuItem @click="handleLogout">
|
||||
<LogOut class="mr-2 h-4 w-4" />
|
||||
<span>Abmelden</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
||||
Reference in New Issue
Block a user