Files
webapp/app/components/UserAvatar.vue
2026-01-07 09:10:35 +07:00

124 lines
3.5 KiB
Vue

<template>
<div class="flex flex-col items-center space-y-4">
<!-- Avatar -->
<div class="relative">
<div
class="w-24 h-24 rounded-full overflow-hidden border-4 border-base-300 shadow-lg"
:class="{ 'animate-pulse bg-base-200': loading }"
>
<div
v-if="!loading && avatarSvg"
v-html="avatarSvg"
class="w-full h-full"
/>
<div
v-else-if="!loading"
class="w-full h-full bg-gradient-to-br from-primary to-primary/70 flex items-center justify-center text-primary-content text-2xl font-bold"
>
{{ initials }}
</div>
</div>
<!-- Change avatar button -->
<button
@click="regenerateAvatar"
:disabled="loading"
class="absolute -bottom-1 -right-1 w-8 h-8 bg-primary hover:bg-primary/80 disabled:bg-base-300 text-primary-content rounded-full flex items-center justify-center shadow-lg transition-colors"
:title="$t('profile.regenerate_avatar')"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</button>
</div>
<!-- User name -->
<div class="text-center">
<p class="font-semibold text-base-content">{{ displayName }}</p>
<p class="text-sm text-base-content/60" v-if="userId">ID: {{ userId }}</p>
</div>
</div>
</template>
<script setup>
const props = defineProps({
userId: String,
firstName: String,
lastName: String,
avatarId: String,
editable: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['avatar-changed'])
const loading = ref(false)
const avatarSvg = ref('')
// Computed properties
const displayName = computed(() => {
const first = props.firstName || ''
const last = props.lastName || ''
return `${first} ${last}`.trim() || 'User'
})
const initials = computed(() => {
const first = props.firstName?.charAt(0) || ''
const last = props.lastName?.charAt(0) || ''
return (first + last).toUpperCase() || '?'
})
// Generate avatar via DiceBear API
const generateAvatar = async (seed) => {
if (!seed) return
try {
loading.value = true
// Use DiceBear API to generate SVG avatar
const response = await fetch(`https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(seed)}&backgroundColor=b6e3f4,c0aede,d1d4f9`)
if (response.ok) {
avatarSvg.value = await response.text()
} else {
console.error('Failed to generate avatar:', response.status)
}
} catch (error) {
console.error('Error generating avatar:', error)
} finally {
loading.value = false
}
}
// Generate new random avatar ID
const regenerateAvatar = async () => {
if (!props.editable || loading.value) return
const newAvatarId = Math.random().toString(36).substring(2, 15)
// Update avatar locally first
await generateAvatar(newAvatarId)
// Notify parent about avatar change
emit('avatar-changed', newAvatarId)
}
// Watch avatarId changes
watch(() => props.avatarId, (newAvatarId) => {
if (newAvatarId) {
generateAvatar(newAvatarId)
}
}, { immediate: true })
// If no avatarId, generate deterministic one based on userId
onMounted(async () => {
if (!props.avatarId && props.userId) {
// Build deterministic ID from userId
const fallbackSeed = props.userId
await generateAvatar(fallbackSeed)
}
})
</script>