Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | <script setup lang="ts">
import { User, Mail, Shield, Calendar, Cloud, ChevronRight, BookOpen, Contact, Key } from 'lucide-vue-next'
import { useAuthStore } from '@/stores/auth.store'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { roleLabelKey, roleBadgeVariant as roleBadgeVariantFor } from '@/i18n/role-labels'
import Badge from '@/components/ui/Badge.vue'
import Card from '@/components/ui/Card.vue'
import PageHeader from '@/components/ui/PageHeader.vue'
const authStore = useAuthStore()
const { t } = useI18n()
// WORKAROUND: Wir überschreiben hier lokal den strengen Typ von authStore.user mit "any".
// So hört TypeScript auf zu meckern, dass firstName, course, etc. nicht im alten Typen existieren.
const user = computed(() => authStore.user as any)
// Zentrale role-label helpers (i18n/role-labels.ts) ersetzen die alten
// Inline-Maps — eine Quelle für Variant + Übersetzung über alle Views.
const roleBadgeVariant = computed(() => roleBadgeVariantFor(user.value?.role))
const roleLabel = computed(() => t(roleLabelKey(user.value?.role)))
const createdDate = computed(() => {
if (!user.value?.created_at) return 'N/A'
return new Date(user.value.created_at).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
})
</script>
<template>
<div class="p-6">
<PageHeader :title="t('UserView.title')" :subtitle="t('UserView.subtitle')" />
<div v-if="!user" class="text-center py-12">
<p class="text-gray-500">{{ t('UserView.loading') }}</p>
</div>
<div v-else class="space-y-6">
<Card class="flex items-center justify-between">
<div class="flex items-center gap-4">
<div
class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center"
>
<User :size="32" class="text-primary" />
</div>
<div>
<div class="font-semibold text-gray-900 text-lg">
{{ user.username || 'N/A' }}
</div>
<Badge :variant="roleBadgeVariant">{{ roleLabel }}</Badge>
</div>
</div>
</Card>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.firstName') }}</div>
<div class="font-medium" :class="user.firstName ? 'text-gray-900' : 'text-gray-400'">
{{ user.firstName || 'N/A' }}
</div>
</div>
<Contact :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.lastName') }}</div>
<div class="font-medium" :class="user.lastName ? 'text-gray-900' : 'text-gray-400'">
{{ user.lastName || 'N/A' }}
</div>
</div>
<Contact :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.email') }}</div>
<div class="font-medium" :class="user.email ? 'text-gray-900' : 'text-gray-400'">
{{ user.email || 'N/A' }}
</div>
</div>
<Mail :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.course') }}</div>
<div class="font-medium" :class="user.course?.name ? 'text-gray-900' : 'text-gray-400'">
{{ user.course?.name || 'N/A' }}
</div>
</div>
<BookOpen :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.role') }}</div>
<div class="font-medium text-gray-900">{{ roleLabel }}</div>
</div>
<Shield :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.userId') }}</div>
<div class="font-mono text-xs" :class="user.userId ? 'text-gray-600' : 'text-gray-400'">
{{ user.userId || 'N/A' }}
</div>
</div>
<User :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.registeredAt') }}</div>
<div class="font-medium text-gray-900">{{ createdDate }}</div>
</div>
<Calendar :size="20" class="text-primary" />
</Card>
<Card class="flex items-center justify-between">
<div>
<div class="text-sm text-gray-500 mb-1">{{ t('UserView.fields.keycloakId') }}</div>
<div class="font-mono text-xs" :class="user.keycloak_id ? 'text-gray-600' : 'text-gray-400'">
{{ user.keycloak_id || 'N/A' }}
</div>
</div>
<Key :size="20" class="text-primary" />
</Card>
</div>
<!-- Settings — eigenes Layout (Listen-Eintrag), nicht Karten-
Grid. Bleibt weiß, gleicher Border/Padding-Stil wie die
Cards drüber. -->
<div class="bg-white rounded-2xl shadow-md border border-gray-100 overflow-hidden">
<div class="px-6 py-4 border-b">
<h2 class="text-lg font-semibold text-gray-900">{{ t('UserView.settings.title') }}</h2>
</div>
<router-link
to="/user/openstack"
class="flex items-center justify-between px-6 py-4 hover:bg-gray-50 transition-colors"
>
<div class="flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
<Cloud :size="20" class="text-primary" />
</div>
<div>
<div class="font-medium text-gray-900">{{ t('UserView.settings.openstackTitle') }}</div>
<div class="text-sm text-gray-500">
{{ t('UserView.settings.openstackHint') }}
</div>
</div>
</div>
<ChevronRight :size="18" class="text-gray-400" />
</router-link>
</div>
</div>
</div>
</template> |